dsh-spectacles 1.0.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +9 -3
- package/README.zh.md +9 -3
- package/index.js +18 -1
- package/package.json +3 -1
- package/scripts/look-region.ps1 +7 -17
- package/scripts/look2.ps1 +33 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
人话版。每次发版都在这儿写一段:改了什么、为什么改、有没有坑。
|
|
4
4
|
|
|
5
|
+
## 1.1.2 — 2026-11-09
|
|
6
|
+
|
|
7
|
+
- 框选不再先抓一帧整屏当底图,只算坐标:铺一层半透明黑、你拖框,它记下「哪个窗口 + 相对它的哪一块」。选的时候不读任何像素。
|
|
8
|
+
- 置顶抓取失败时不再硬出图。有些程序(提权运行的、自带保护的)不让被提到最前,这时候会明确报 `raise-failed`,而不是给你一张压着别人窗口的画面。
|
|
9
|
+
- 补了两个关键词(`dsh-plugin`、`deepseek-harness`),这样插件市场的自动收录能扫到我们。
|
|
10
|
+
|
|
11
|
+
## 1.1.0 — 2026-11-09
|
|
12
|
+
|
|
13
|
+
- 抓取任何窗口时,会先把该窗口短暂置顶一下,再抓画面,然后立刻放回去。这样 GPU 绘制的窗口(游戏、播放器)也能抓到。抓的那一下屏幕上会闪一下。
|
|
14
|
+
- 读不到时会明确报错,不会拿别的东西顶替。
|
|
15
|
+
|
|
5
16
|
## V1.0.0 — 2026-11-09
|
|
6
17
|
|
|
7
18
|
**这是第一个我们认它是 V1 的版本。** 下面那些 1.0.0 / 1.0.1 / 1.1.0 / 1.1.1 都是试验期的编号,实际只到 0.5 的水平——留着当历史,不再对外提。
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Spectacles · 砚明的金丝眼镜
|
|
2
2
|
|
|
3
|
-
**V1** — one job: point at a window, read its text clearly. Nothing else is in here on purpose.
|
|
3
|
+
**V1.1** — one job: point at a window, read its text clearly. Nothing else is in here on purpose.
|
|
4
4
|
|
|
5
5
|
**Point at one window. Let the AI read it clearly.**
|
|
6
6
|
|
|
@@ -48,7 +48,13 @@ So the plugin does three things: **where to look, how much to enlarge, and when
|
|
|
48
48
|
## Install
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
dsh plugin --profile desktop add
|
|
51
|
+
dsh plugin --profile desktop add dsh-spectacles
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If you'd rather pin the source (no registry), this works too:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.1.0
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
Restart DSH. A pill button labelled **戴上眼镜** (put on the glasses) appears in the bottom-right corner.
|
|
@@ -138,4 +144,4 @@ You don't choose — just speak plainly.
|
|
|
138
144
|
|
|
139
145
|
*Spectacles* — in Chinese, 砚明的金丝眼镜 ("Yanming's gold-rimmed glasses"). Yanming is the AI assistant who lives on the author's machine; she's nearsighted, so she got a pair of glasses.
|
|
140
146
|
|
|
141
|
-
Apache-2.0.
|
|
147
|
+
Apache-2.0.
|
package/README.zh.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 砚明的金丝眼镜 · Spectacles
|
|
2
2
|
|
|
3
|
-
**V1 版** —— 只干一件事:指定一个窗口(或框住它里面的一块),把里面的字读清楚。别的都故意没放进来。
|
|
3
|
+
**V1.1 版** —— 只干一件事:指定一个窗口(或框住它里面的一块),把里面的字读清楚。别的都故意没放进来。
|
|
4
4
|
|
|
5
5
|
**指定一个窗口,让 AI 把里面的字看清楚。**
|
|
6
6
|
|
|
@@ -48,7 +48,13 @@ DSH 默认只能看你粘进去的东西。这个插件补的是中间那段:*
|
|
|
48
48
|
## 装
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
dsh plugin --profile desktop add
|
|
51
|
+
dsh plugin --profile desktop add dsh-spectacles
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
不想走 npm 也行,直接钉源码:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.1.0
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
装完重启 DSH,右下角会多出一个胶囊按钮「戴上眼镜」。
|
|
@@ -138,4 +144,4 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\selftest.ps1
|
|
|
138
144
|
|
|
139
145
|
「砚明的金丝眼镜」——砚明是作者家里住在这台电脑上的 AI 助手,她近视,所以给她配了副眼镜。英文名 Spectacles。
|
|
140
146
|
|
|
141
|
-
协议 Apache-2.0。
|
|
147
|
+
协议 Apache-2.0。
|
package/index.js
CHANGED
|
@@ -179,16 +179,31 @@ function runPs(script, argv) {
|
|
|
179
179
|
})
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
function captureHint(cap) {
|
|
183
|
+
if (!cap || cap.ok) return null
|
|
184
|
+
if (cap.error === 'printwindow-failed') {
|
|
185
|
+
return (
|
|
186
|
+
'这个窗口不吃「重画」这条请求(GPU 直绘的窗口都这样),所以抓不到它的画面。' +
|
|
187
|
+
'要么把它切到最前再让我看;要么允许临时置顶抓一帧(DSH_LOOK_RAISE=1,抓的那一下屏幕会闪一下)。'
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
192
|
+
|
|
182
193
|
function runLook(args, via) {
|
|
183
194
|
guardWindow('look', args)
|
|
184
195
|
guardOut(args)
|
|
185
196
|
const p = runLookRaw(args)
|
|
186
197
|
return p.then((cap) => {
|
|
187
|
-
audit({ tool: 'look', window: args.window || null, crop: args.crop || null, via: via || null, region: (cap && cap.region) || null, png: (cap && cap.png) || null, ok: !!(cap && cap.ok) })
|
|
198
|
+
audit({ tool: 'look', window: args.window || null, crop: args.crop || null, via: via || null, source: (cap && cap.source) || null, region: (cap && cap.region) || null, png: (cap && cap.png) || null, ok: !!(cap && cap.ok) })
|
|
199
|
+
const hint = captureHint(cap)
|
|
200
|
+
if (hint) throw new Error(hint)
|
|
188
201
|
return cap
|
|
189
202
|
})
|
|
190
203
|
}
|
|
191
204
|
|
|
205
|
+
const RAISE = String(process.env.DSH_LOOK_RAISE || '') !== '0'
|
|
206
|
+
|
|
192
207
|
function runLookRaw(args) {
|
|
193
208
|
if (args.window) {
|
|
194
209
|
const argv = ['-Window', String(args.window)]
|
|
@@ -196,6 +211,8 @@ function runLookRaw(args) {
|
|
|
196
211
|
if (typeof args.scale === 'number' && args.scale > 0) argv.push('-Scale', String(args.scale))
|
|
197
212
|
if (args.image === true) argv.push('-Image')
|
|
198
213
|
if (args.out) argv.push('-Out', String(args.out))
|
|
214
|
+
// 这个窗口不吃 PrintWindow(GPU 直绘)时,临时把它提到最上层、从屏幕抓它那一块、立刻还原。
|
|
215
|
+
if (RAISE) argv.push('-Raise')
|
|
199
216
|
return runPs(WIN_SCRIPT, argv)
|
|
200
217
|
}
|
|
201
218
|
const argv = []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-spectacles",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Point at one window and let the agent read it clearly. Window scoped screen reading for DeepSeek Harness: look (numbers first), read_text (OCR), see (crop + zoom handed to the host vision).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
],
|
|
27
27
|
"keywords": [
|
|
28
28
|
"deepseek",
|
|
29
|
+
"deepseek-harness",
|
|
29
30
|
"dsh",
|
|
31
|
+
"dsh-plugin",
|
|
30
32
|
"plugin",
|
|
31
33
|
"vision",
|
|
32
34
|
"look",
|
package/scripts/look-region.ps1
CHANGED
|
@@ -31,19 +31,15 @@ public class Rg {
|
|
|
31
31
|
|
|
32
32
|
$vs = [System.Windows.Forms.SystemInformation]::VirtualScreen
|
|
33
33
|
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
$
|
|
37
|
-
$
|
|
38
|
-
$g0.Dispose()
|
|
39
|
-
|
|
40
|
-
$script:bg = $bg
|
|
34
|
+
# 不抓屏幕:这层遮罩只是一块半透明的、能透过它看见下面东西的玻璃,
|
|
35
|
+
# 用来接你的拖动、记下矩形坐标。框选本身不产生任何画面内容。
|
|
36
|
+
$script:W = $vs.Width
|
|
37
|
+
$script:H = $vs.Height
|
|
41
38
|
$script:sel = $null
|
|
42
39
|
$script:dragging = $false
|
|
43
40
|
$script:done = $false
|
|
44
41
|
$script:cancelled = $false
|
|
45
42
|
$script:start = $null
|
|
46
|
-
$script:dim = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(120, 0, 0, 0))
|
|
47
43
|
$script:pen = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 0, 205, 120)), 2
|
|
48
44
|
$script:penDot = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(200, 255, 255, 255)), 1
|
|
49
45
|
$script:font = New-Object System.Drawing.Font 'Microsoft YaHei UI', 12, ([System.Drawing.FontStyle]::Bold)
|
|
@@ -59,13 +55,14 @@ $form.ShowInTaskbar = $false
|
|
|
59
55
|
$form.Cursor = [System.Windows.Forms.Cursors]::Cross
|
|
60
56
|
$form.KeyPreview = $true
|
|
61
57
|
$form.BackColor = [System.Drawing.Color]::Black
|
|
58
|
+
$form.Opacity = 0.22
|
|
62
59
|
try { $form.GetType().GetProperty('DoubleBuffered', 'Instance,NonPublic').SetValue($form, $true) } catch {}
|
|
63
60
|
|
|
64
61
|
function Draw-Tag($g, $text, $x, $y) {
|
|
65
62
|
$sz = $g.MeasureString($text, $script:font)
|
|
66
63
|
$w = [int]$sz.Width + 16; $h = [int]$sz.Height + 8
|
|
67
|
-
$bx = [Math]::Max(0, [Math]::Min($x, $script:
|
|
68
|
-
$by = [Math]::Max(0, [Math]::Min($y, $script:
|
|
64
|
+
$bx = [Math]::Max(0, [Math]::Min($x, $script:W - $w))
|
|
65
|
+
$by = [Math]::Max(0, [Math]::Min($y, $script:H - $h))
|
|
69
66
|
$g.FillRectangle($script:brBox, $bx, $by, $w, $h)
|
|
70
67
|
$g.DrawString($text, $script:font, $script:brText, ($bx + 8), ($by + 4))
|
|
71
68
|
}
|
|
@@ -73,19 +70,12 @@ function Draw-Tag($g, $text, $x, $y) {
|
|
|
73
70
|
$form.Add_Paint({
|
|
74
71
|
param($s, $e)
|
|
75
72
|
$g = $e.Graphics
|
|
76
|
-
$g.DrawImageUnscaled($script:bg, 0, 0)
|
|
77
73
|
$r = $script:sel
|
|
78
74
|
if ($r -and $r.Width -gt 0 -and $r.Height -gt 0) {
|
|
79
|
-
$W = $script:bg.Width; $H = $script:bg.Height
|
|
80
|
-
$g.FillRectangle($script:dim, 0, 0, $W, [Math]::Max(0, $r.Top))
|
|
81
|
-
$g.FillRectangle($script:dim, 0, $r.Bottom, $W, [Math]::Max(0, $H - $r.Bottom))
|
|
82
|
-
$g.FillRectangle($script:dim, 0, $r.Top, [Math]::Max(0, $r.Left), $r.Height)
|
|
83
|
-
$g.FillRectangle($script:dim, $r.Right, $r.Top, [Math]::Max(0, $W - $r.Right), $r.Height)
|
|
84
75
|
$g.DrawRectangle($script:pen, $r)
|
|
85
76
|
Draw-Tag $g ("{0} x {1}" -f $r.Width, $r.Height) ($r.Left + 6) ($r.Top + 6)
|
|
86
77
|
}
|
|
87
78
|
else {
|
|
88
|
-
$g.FillRectangle($script:dim, 0, 0, $script:bg.Width, $script:bg.Height)
|
|
89
79
|
Draw-Tag $g '拖一个框;Esc 取消' 40 40
|
|
90
80
|
}
|
|
91
81
|
})
|
package/scripts/look2.ps1
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[string]$Crop = "",
|
|
4
4
|
[double]$Scale = 1.0,
|
|
5
5
|
[switch]$Image,
|
|
6
|
+
[switch]$Raise,
|
|
6
7
|
[switch]$List,
|
|
7
8
|
[string]$Out = "",
|
|
8
9
|
[string]$SaveDir = ""
|
|
@@ -28,6 +29,7 @@ public class W2 {
|
|
|
28
29
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
29
30
|
[DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr h, int idx);
|
|
30
31
|
[DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags);
|
|
32
|
+
[DllImport("user32.dll", SetLastError = true)] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
|
31
33
|
|
|
32
34
|
public struct RECT { public int Left, Top, Right, Bottom; }
|
|
33
35
|
|
|
@@ -117,7 +119,36 @@ $hdc = $g.GetHdc()
|
|
|
117
119
|
$ok = [W2]::PrintWindow($w.H, $hdc, 2)
|
|
118
120
|
$g.ReleaseHdc($hdc)
|
|
119
121
|
$g.Dispose()
|
|
120
|
-
|
|
122
|
+
$source = 'printwindow'
|
|
123
|
+
if (-not $ok) {
|
|
124
|
+
if (-not $Raise) {
|
|
125
|
+
@{ ok = $false; error = 'printwindow-failed' } | ConvertTo-Json -Compress
|
|
126
|
+
exit 1
|
|
127
|
+
}
|
|
128
|
+
# 这个窗口不吃「重画」这条请求(GPU 直绘常见)。临时把它提到最上层,从屏幕抓它那一块,再立刻还原。
|
|
129
|
+
# 不抢焦点(NOACTIVATE)、不移动、不改大小。
|
|
130
|
+
$exBefore = [W2]::GetWindowLong($w.H, -20)
|
|
131
|
+
$wasTop = (($exBefore -band 0x0008) -ne 0)
|
|
132
|
+
$raised = [W2]::SetWindowPos($w.H, [IntPtr](-1), 0, 0, 0, 0, (0x0002 -bor 0x0001 -bor 0x0010))
|
|
133
|
+
if (-not $raised) {
|
|
134
|
+
# 置顶被拒(有些游戏/带反截屏保护的程序会拒绝 SetWindowPos)。这时屏幕那块是别的窗口,抓了就是错图,宁可报错。
|
|
135
|
+
$werr = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
|
|
136
|
+
@{ ok = $false; error = 'raise-failed'; win32Error = $werr; hint = '这个窗口不让被提到最前,此时抓屏会抓到压在上面的窗口,所以没有出图' } | ConvertTo-Json -Compress
|
|
137
|
+
exit 1
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
Start-Sleep -Milliseconds 90
|
|
141
|
+
$g2 = [System.Drawing.Graphics]::FromImage($full)
|
|
142
|
+
$sz = New-Object System.Drawing.Size ([int]$ww), ([int]$wh)
|
|
143
|
+
$g2.CopyFromScreen([int]$w.L, [int]$w.T, 0, 0, $sz)
|
|
144
|
+
$g2.Dispose()
|
|
145
|
+
$source = 'screen-raised'
|
|
146
|
+
} finally {
|
|
147
|
+
if (-not $wasTop) {
|
|
148
|
+
[W2]::SetWindowPos($w.H, [IntPtr](-2), 0, 0, 0, 0, (0x0002 -bor 0x0001 -bor 0x0010)) | Out-Null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
121
152
|
|
|
122
153
|
# 可选区域裁剪(相对窗口左上角)
|
|
123
154
|
$rect = New-Object System.Drawing.Rectangle 0, 0, $ww, $wh
|
|
@@ -198,7 +229,7 @@ $avg = "#{0:X2}{1:X2}{2:X2}" -f [int]($sumR / $n), [int]($sumG / $n), [int]($sum
|
|
|
198
229
|
$res = [ordered]@{
|
|
199
230
|
ok = $true
|
|
200
231
|
when = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff')
|
|
201
|
-
source =
|
|
232
|
+
source = $source
|
|
202
233
|
window = [ordered]@{ hwnd = [int64]$w.H; title = $w.Title; pid = $w.Pid; rect = @($w.L, $w.T, $ww, $wh) }
|
|
203
234
|
region = @($rect.X, $rect.Y, $rect.Width, $rect.Height)
|
|
204
235
|
avgColor = $avg
|