dsh-desktop-windows 0.2.2 → 0.2.4
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/package.json +1 -1
- package/src/main.js +31 -3
- package/src/screenshot.html +156 -56
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-desktop-windows",
|
|
3
3
|
"productName": "DeepSeek Harness Desktop",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"description": "DeepSeek Harness 桌面版(Windows)· Electron desktop shell for DeepSeek Harness (dsh web) — 双击即用,无需终端 / double-click to run the harness in a standalone window",
|
|
6
6
|
"main": "src/main.js",
|
|
7
7
|
"license": "MIT",
|
package/src/main.js
CHANGED
|
@@ -18,6 +18,7 @@ const { spawn, execFile } = require('node:child_process')
|
|
|
18
18
|
const http = require('node:http')
|
|
19
19
|
const fs = require('node:fs')
|
|
20
20
|
const path = require('node:path')
|
|
21
|
+
const os = require('node:os')
|
|
21
22
|
|
|
22
23
|
const DEFAULT_PORT = 3080
|
|
23
24
|
const START_TIMEOUT_MS = 90_000
|
|
@@ -52,6 +53,15 @@ function log(...args) {
|
|
|
52
53
|
console.log(LOG_PREFIX, ...args)
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/** 截图流程日志(追加写入 ~/.dsh/logs/screenshot.log,便于诊断)。 */
|
|
57
|
+
function shotLog(...args) {
|
|
58
|
+
try {
|
|
59
|
+
const line = `[${new Date().toISOString()}] ${args.join(' ')}`
|
|
60
|
+
console.log('[shot]', ...args)
|
|
61
|
+
fs.appendFileSync(path.join(os.homedir(), '.dsh', 'logs', 'screenshot.log'), line + '\n')
|
|
62
|
+
} catch { /* 忽略日志错误 */ }
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
/* ------------------------------------------------------------------ *
|
|
56
66
|
* 工具:探测 / 等待 HTTP 服务
|
|
57
67
|
* ------------------------------------------------------------------ */
|
|
@@ -437,6 +447,7 @@ function openShotWindow() {
|
|
|
437
447
|
shotWindow.webContents.once('did-finish-load', () => {
|
|
438
448
|
if (pendingShot && !shotWindow.isDestroyed()) {
|
|
439
449
|
shotWindow.webContents.send('screenshot:data', pendingShot.toDataURL())
|
|
450
|
+
shotLog('shot: data sent to selector window')
|
|
440
451
|
}
|
|
441
452
|
})
|
|
442
453
|
shotWindow.on('closed', () => {
|
|
@@ -454,11 +465,15 @@ function onShotLoaded() {
|
|
|
454
465
|
|
|
455
466
|
/** 选区确认:裁剪 → 剪贴板 → 恢复主窗口并粘贴。 */
|
|
456
467
|
function onShotDone(rect) {
|
|
468
|
+
shotLog('shot: done called', rect ? JSON.stringify(rect) : 'null')
|
|
457
469
|
const image = pendingShot
|
|
458
470
|
pendingShot = null
|
|
459
471
|
if (shotWindow && !shotWindow.isDestroyed()) shotWindow.destroy()
|
|
460
472
|
showMainWindow() // 恢复显示聊天窗口
|
|
461
|
-
if (!image || !rect)
|
|
473
|
+
if (!image || !rect) {
|
|
474
|
+
shotLog('shot: done skipped (no image or rect)')
|
|
475
|
+
return
|
|
476
|
+
}
|
|
462
477
|
const sf = screen.getPrimaryDisplay().scaleFactor || 1
|
|
463
478
|
const r = {
|
|
464
479
|
x: Math.round(rect.x * sf),
|
|
@@ -466,13 +481,21 @@ function onShotDone(rect) {
|
|
|
466
481
|
width: Math.round(rect.w * sf),
|
|
467
482
|
height: Math.round(rect.h * sf),
|
|
468
483
|
}
|
|
469
|
-
if (r.width <= 0 || r.height <= 0)
|
|
484
|
+
if (r.width <= 0 || r.height <= 0) {
|
|
485
|
+
shotLog('shot: invalid crop rect after scale')
|
|
486
|
+
return
|
|
487
|
+
}
|
|
470
488
|
const cropped = image.crop(r)
|
|
489
|
+
shotLog('shot: cropped', cropped.getSize().width + 'x' + cropped.getSize().height)
|
|
471
490
|
// 保存一份临时文件(验证/诊断用)
|
|
472
491
|
try {
|
|
473
492
|
fs.writeFileSync(path.join(os.tmpdir(), 'dsh-screenshot.png'), cropped.toPNG())
|
|
474
|
-
|
|
493
|
+
shotLog('shot: saved temp png')
|
|
494
|
+
} catch (e) {
|
|
495
|
+
shotLog('shot: temp save failed', String(e && e.message || e))
|
|
496
|
+
}
|
|
475
497
|
clipboard.writeImage(cropped)
|
|
498
|
+
shotLog('shot: written to clipboard')
|
|
476
499
|
// 等主窗口就绪后尝试自动粘贴;若聚焦失败则提示手动 Ctrl+V
|
|
477
500
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
478
501
|
setTimeout(async () => {
|
|
@@ -658,6 +681,7 @@ if (!gotLock) {
|
|
|
658
681
|
})
|
|
659
682
|
// 选区截图:隐藏主窗口 → 截全屏 → 选区窗口 → 裁剪 → 剪贴板 → 粘贴
|
|
660
683
|
ipcMain.handle('dsh-desktop:capture', async () => {
|
|
684
|
+
shotLog('capture: requested')
|
|
661
685
|
try {
|
|
662
686
|
// 先隐藏主窗口,避免截到聊天框本身
|
|
663
687
|
if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide()
|
|
@@ -673,17 +697,21 @@ if (!gotLock) {
|
|
|
673
697
|
})
|
|
674
698
|
if (!sources.length) {
|
|
675
699
|
showMainWindow()
|
|
700
|
+
shotLog('capture: no screen source')
|
|
676
701
|
return { ok: false, error: '未找到屏幕源' }
|
|
677
702
|
}
|
|
678
703
|
pendingShot = sources[0].thumbnail
|
|
679
704
|
if (pendingShot.isEmpty()) {
|
|
680
705
|
showMainWindow()
|
|
706
|
+
shotLog('capture: empty thumbnail')
|
|
681
707
|
return { ok: false, error: '截图为空' }
|
|
682
708
|
}
|
|
709
|
+
shotLog('capture: captured', pendingShot.getSize().width + 'x' + pendingShot.getSize().height)
|
|
683
710
|
openShotWindow()
|
|
684
711
|
return { ok: true }
|
|
685
712
|
} catch (e) {
|
|
686
713
|
showMainWindow()
|
|
714
|
+
shotLog('capture: error', String((e && e.message) || e))
|
|
687
715
|
return { ok: false, error: String((e && e.message) || e) }
|
|
688
716
|
}
|
|
689
717
|
})
|
package/src/screenshot.html
CHANGED
|
@@ -8,9 +8,22 @@
|
|
|
8
8
|
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; cursor: crosshair; }
|
|
9
9
|
#stage { position: fixed; inset: 0; }
|
|
10
10
|
#stage img { width: 100%; height: 100%; object-fit: fill; display: block; }
|
|
11
|
-
|
|
12
|
-
#sel {
|
|
13
|
-
|
|
11
|
+
.mask { position: fixed; background: rgba(0, 0, 0, 0.45); pointer-events: none; }
|
|
12
|
+
#sel {
|
|
13
|
+
position: fixed; border: 1.5px solid #2563eb;
|
|
14
|
+
background: rgba(37, 99, 235, 0.10);
|
|
15
|
+
display: none; pointer-events: none; z-index: 3;
|
|
16
|
+
}
|
|
17
|
+
#sel .sz {
|
|
18
|
+
position: absolute; left: 0; top: -22px;
|
|
19
|
+
background: #2563eb; color: #fff;
|
|
20
|
+
font: 11px/18px sans-serif; padding: 0 6px; border-radius: 4px; white-space: nowrap;
|
|
21
|
+
}
|
|
22
|
+
.handle {
|
|
23
|
+
position: fixed; width: 9px; height: 9px; background: #2563eb;
|
|
24
|
+
border: 1px solid #fff; border-radius: 2px; display: none;
|
|
25
|
+
pointer-events: auto; z-index: 4;
|
|
26
|
+
}
|
|
14
27
|
#toolbar {
|
|
15
28
|
position: fixed; display: none; align-items: center; gap: 8px;
|
|
16
29
|
background: rgba(15, 23, 42, 0.92); border: 1px solid #334155;
|
|
@@ -30,92 +43,179 @@
|
|
|
30
43
|
position: fixed; left: 50%; top: 20px; transform: translateX(-50%);
|
|
31
44
|
background: rgba(15, 23, 42, 0.85); color: #e2e8f0;
|
|
32
45
|
font: 13px/1 sans-serif; padding: 8px 16px; border-radius: 8px;
|
|
33
|
-
z-index: 5; pointer-events: none;
|
|
46
|
+
z-index: 5; pointer-events: none; transition: opacity .15s;
|
|
34
47
|
}
|
|
35
48
|
</style>
|
|
36
49
|
</head>
|
|
37
50
|
<body>
|
|
38
51
|
<div id="stage"><img id="bg" alt=""></div>
|
|
39
|
-
<div id="mask"></div>
|
|
52
|
+
<div id="maskTop" class="mask"></div>
|
|
53
|
+
<div id="maskBottom" class="mask"></div>
|
|
54
|
+
<div id="maskLeft" class="mask"></div>
|
|
55
|
+
<div id="maskRight" class="mask"></div>
|
|
40
56
|
<div id="sel"><div class="sz"></div></div>
|
|
57
|
+
<div id="hN" class="handle" style="cursor:n-resize"></div>
|
|
58
|
+
<div id="hS" class="handle" style="cursor:s-resize"></div>
|
|
59
|
+
<div id="hE" class="handle" style="cursor:e-resize"></div>
|
|
60
|
+
<div id="hW" class="handle" style="cursor:w-resize"></div>
|
|
61
|
+
<div id="hNE" class="handle" style="cursor:ne-resize"></div>
|
|
62
|
+
<div id="hNW" class="handle" style="cursor:nw-resize"></div>
|
|
63
|
+
<div id="hSE" class="handle" style="cursor:se-resize"></div>
|
|
64
|
+
<div id="hSW" class="handle" style="cursor:sw-resize"></div>
|
|
41
65
|
<div id="toolbar">
|
|
42
|
-
<span class="tip"
|
|
66
|
+
<span class="tip" id="tip">拖动边框微调选区</span>
|
|
43
67
|
<button id="okBtn">✔ 确定</button>
|
|
44
68
|
<button id="cancelBtn">✕ 取消 (Esc)</button>
|
|
45
69
|
</div>
|
|
46
|
-
<div id="hint">拖动鼠标框选要截取的区域 · Esc 取消</div>
|
|
70
|
+
<div id="hint">拖动鼠标框选要截取的区域 · 画完后可拖拽边框微调 · Esc 取消</div>
|
|
47
71
|
|
|
48
72
|
<script>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
var bg = document.getElementById('bg')
|
|
74
|
+
var sel = document.getElementById('sel')
|
|
75
|
+
var toolbar = document.getElementById('toolbar')
|
|
76
|
+
var hint = document.getElementById('hint')
|
|
77
|
+
var tip = document.getElementById('tip')
|
|
78
|
+
var sz = sel.querySelector('.sz')
|
|
79
|
+
var masks = {
|
|
80
|
+
top: document.getElementById('maskTop'),
|
|
81
|
+
bottom: document.getElementById('maskBottom'),
|
|
82
|
+
left: document.getElementById('maskLeft'),
|
|
83
|
+
right: document.getElementById('maskRight'),
|
|
84
|
+
}
|
|
85
|
+
var handles = {
|
|
86
|
+
n: document.getElementById('hN'), s: document.getElementById('hS'),
|
|
87
|
+
e: document.getElementById('hE'), w: document.getElementById('hW'),
|
|
88
|
+
ne: document.getElementById('hNE'), nw: document.getElementById('hNW'),
|
|
89
|
+
se: document.getElementById('hSE'), sw: document.getElementById('hSW'),
|
|
90
|
+
}
|
|
91
|
+
var W = window.innerWidth, H = window.innerHeight
|
|
92
|
+
var MIN = 8
|
|
93
|
+
var rect = null
|
|
94
|
+
var mode = 'draw' // draw | adjust
|
|
95
|
+
var drag = null
|
|
58
96
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
bg.onload = () => {
|
|
97
|
+
window.__screenshot.onData(function (dataUrl) {
|
|
98
|
+
bg.onload = function () {
|
|
62
99
|
window.__screenshot.loaded()
|
|
63
|
-
setTimeout(()
|
|
100
|
+
setTimeout(function () { hint.style.opacity = '0.9' }, 100)
|
|
64
101
|
}
|
|
65
102
|
bg.src = dataUrl
|
|
66
103
|
})
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
105
|
+
function clampRect(r) {
|
|
106
|
+
var x = Math.max(0, Math.min(W - MIN, r.x))
|
|
107
|
+
var y = Math.max(0, Math.min(H - MIN, r.y))
|
|
108
|
+
var w = Math.min(r.w, W - x)
|
|
109
|
+
var h = Math.min(r.h, H - y)
|
|
110
|
+
w = Math.max(MIN, w); h = Math.max(MIN, h)
|
|
111
|
+
return { x: x, y: y, w: w, h: h }
|
|
74
112
|
}
|
|
75
113
|
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
drawing = true
|
|
80
|
-
rect = null
|
|
81
|
-
sel.style.display = 'none'
|
|
82
|
-
toolbar.style.display = 'none'
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
document.addEventListener('mousemove', (e) => {
|
|
86
|
-
if (!drawing) return
|
|
87
|
-
const x = Math.min(startX, e.clientX)
|
|
88
|
-
const y = Math.min(startY, e.clientY)
|
|
89
|
-
const w = Math.abs(e.clientX - startX)
|
|
90
|
-
const h = Math.abs(e.clientY - startY)
|
|
91
|
-
rect = { x, y, w, h }
|
|
114
|
+
function layout() {
|
|
115
|
+
if (!rect) return
|
|
116
|
+
var x = rect.x, y = rect.y, w = rect.w, h = rect.h
|
|
92
117
|
sel.style.display = 'block'
|
|
93
118
|
sel.style.left = x + 'px'; sel.style.top = y + 'px'
|
|
94
119
|
sel.style.width = w + 'px'; sel.style.height = h + 'px'
|
|
95
120
|
sz.textContent = Math.round(w) + ' × ' + Math.round(h)
|
|
96
|
-
|
|
121
|
+
// 遮罩(4 块)
|
|
122
|
+
masks.top.style.cssText = 'left:0;top:0;width:' + W + 'px;height:' + y + 'px;display:block'
|
|
123
|
+
masks.bottom.style.cssText = 'left:0;top:' + (y + h) + 'px;width:' + W + 'px;height:' + (H - y - h) + 'px;display:block'
|
|
124
|
+
masks.left.style.cssText = 'left:0;top:' + y + 'px;width:' + x + 'px;height:' + h + 'px;display:block'
|
|
125
|
+
masks.right.style.cssText = 'left:' + (x + w) + 'px;top:' + y + 'px;width:' + (W - x - w) + 'px;height:' + h + 'px;display:block'
|
|
126
|
+
// 手柄
|
|
127
|
+
if (mode === 'adjust') {
|
|
128
|
+
var half = 4.5
|
|
129
|
+
handles.n.style.cssText = 'left:' + (x + w / 2 - half) + 'px;top:' + (y - half) + 'px;display:block'
|
|
130
|
+
handles.s.style.cssText = 'left:' + (x + w / 2 - half) + 'px;top:' + (y + h - half) + 'px;display:block'
|
|
131
|
+
handles.e.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y + h / 2 - half) + 'px;display:block'
|
|
132
|
+
handles.w.style.cssText = 'left:' + (x - half) + 'px;top:' + (y + h / 2 - half) + 'px;display:block'
|
|
133
|
+
handles.ne.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y - half) + 'px;display:block'
|
|
134
|
+
handles.nw.style.cssText = 'left:' + (x - half) + 'px;top:' + (y - half) + 'px;display:block'
|
|
135
|
+
handles.se.style.cssText = 'left:' + (x + w - half) + 'px;top:' + (y + h - half) + 'px;display:block'
|
|
136
|
+
handles.sw.style.cssText = 'left:' + (x - half) + 'px;top:' + (y + h - half) + 'px;display:block'
|
|
137
|
+
} else {
|
|
138
|
+
Object.keys(handles).forEach(function (k) { handles[k].style.display = 'none' })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function inside(e, r) {
|
|
143
|
+
return e.clientX >= r.x && e.clientX <= r.x + r.w && e.clientY >= r.y && e.clientY <= r.y + r.h
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
document.addEventListener('mousedown', function (e) {
|
|
147
|
+
if (e.button !== 0) return
|
|
148
|
+
if (mode === 'draw') {
|
|
149
|
+
drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
|
|
150
|
+
} else {
|
|
151
|
+
// 调整模式:手柄 / 拖动移动 / 外部重画
|
|
152
|
+
var hKey = null
|
|
153
|
+
for (var k in handles) if (e.target === handles[k]) { hKey = k; break }
|
|
154
|
+
if (hKey) {
|
|
155
|
+
drag = { type: 'resize', dir: hKey, x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y, w: rect.w, h: rect.h } }
|
|
156
|
+
} else if (inside(e, rect)) {
|
|
157
|
+
drag = { type: 'move', x0: e.clientX, y0: e.clientY, orig: { x: rect.x, y: rect.y } }
|
|
158
|
+
} else {
|
|
159
|
+
// 点击选区外 → 重新画
|
|
160
|
+
mode = 'draw'
|
|
161
|
+
rect = null
|
|
162
|
+
sel.style.display = 'none'
|
|
163
|
+
toolbar.style.display = 'none'
|
|
164
|
+
Object.keys(handles).forEach(function (k) { handles[k].style.display = 'none' })
|
|
165
|
+
masks.top.style.display = masks.bottom.style.display = masks.left.style.display = masks.right.style.display = 'none'
|
|
166
|
+
hint.style.opacity = '0.9'
|
|
167
|
+
drag = { type: 'draw', x0: e.clientX, y0: e.clientY }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
document.addEventListener('mousemove', function (e) {
|
|
173
|
+
if (!drag) return
|
|
174
|
+
var dx = e.clientX - drag.x0
|
|
175
|
+
var dy = e.clientY - drag.y0
|
|
176
|
+
if (drag.type === 'draw') {
|
|
177
|
+
rect = clampRect({ x: Math.min(drag.x0, e.clientX), y: Math.min(drag.y0, e.clientY), w: Math.abs(e.clientX - drag.x0), h: Math.abs(e.clientY - drag.y0) })
|
|
178
|
+
layout()
|
|
179
|
+
} else if (drag.type === 'move') {
|
|
180
|
+
rect = clampRect({ x: drag.orig.x + dx, y: drag.orig.y + dy, w: rect.w, h: rect.h })
|
|
181
|
+
layout()
|
|
182
|
+
} else if (drag.type === 'resize') {
|
|
183
|
+
var o = drag.orig
|
|
184
|
+
var r = { x: o.x, y: o.y, w: o.w, h: o.h }
|
|
185
|
+
if (drag.dir.indexOf('e') !== -1) r.w = o.w + dx
|
|
186
|
+
if (drag.dir.indexOf('s') !== -1) r.h = o.h + dy
|
|
187
|
+
if (drag.dir.indexOf('w') !== -1) { r.x = o.x + dx; r.w = o.w - dx }
|
|
188
|
+
if (drag.dir.indexOf('n') !== -1) { r.y = o.y + dy; r.h = o.h - dy }
|
|
189
|
+
rect = clampRect(r)
|
|
190
|
+
layout()
|
|
191
|
+
}
|
|
97
192
|
})
|
|
98
193
|
|
|
99
|
-
document.addEventListener('mouseup', ()
|
|
100
|
-
if (!
|
|
101
|
-
|
|
102
|
-
if (rect && rect.w
|
|
103
|
-
|
|
194
|
+
document.addEventListener('mouseup', function () {
|
|
195
|
+
if (!drag) return
|
|
196
|
+
drag = null
|
|
197
|
+
if (mode === 'draw' && rect && rect.w >= MIN && rect.h >= MIN) {
|
|
198
|
+
mode = 'adjust'
|
|
199
|
+
var t = document.getElementById('toolbar')
|
|
104
200
|
t.style.display = 'flex'
|
|
105
|
-
t.style.left = Math.min(rect.x + rect.w + 10,
|
|
106
|
-
t.style.top = (rect.y + rect.h +
|
|
201
|
+
t.style.left = Math.min(rect.x + rect.w + 10, W - 230) + 'px'
|
|
202
|
+
t.style.top = (rect.y + rect.h + 14 > H - 60 ? rect.y - 58 : rect.y + rect.h + 14) + 'px'
|
|
203
|
+
tip.textContent = '可拖拽边框微调 · 点确定或双击完成'
|
|
107
204
|
hint.style.opacity = '0'
|
|
108
|
-
|
|
109
|
-
rect = null; sel.style.display = 'none'
|
|
205
|
+
layout() // 显示手柄
|
|
110
206
|
}
|
|
111
207
|
})
|
|
112
208
|
|
|
113
|
-
|
|
209
|
+
// 双击选区 → 直接提交
|
|
210
|
+
document.addEventListener('dblclick', function (e) {
|
|
211
|
+
if (rect && inside(e, rect)) window.__screenshot.done(rect)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
document.getElementById('okBtn').onclick = function () {
|
|
114
215
|
if (rect) window.__screenshot.done(rect)
|
|
115
216
|
}
|
|
116
|
-
document.getElementById('cancelBtn').onclick = ()
|
|
117
|
-
|
|
118
|
-
document.addEventListener('keydown', (e) => {
|
|
217
|
+
document.getElementById('cancelBtn').onclick = function () { window.__screenshot.cancel() }
|
|
218
|
+
document.addEventListener('keydown', function (e) {
|
|
119
219
|
if (e.key === 'Escape') window.__screenshot.cancel()
|
|
120
220
|
if (e.key === 'Enter' && rect) window.__screenshot.done(rect)
|
|
121
221
|
})
|