dsh-desktop-windows 0.2.8 → 0.3.1
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/README.en.md +5 -1
- package/README.md +5 -1
- package/package.json +3 -1
- package/src/main.js +236 -6
package/README.en.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
8
|
[]()
|
|
9
9
|
[](https://github.com/ReachGa0/dsh-desktop/actions)
|
|
10
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
11
|
|
|
11
12
|
An **Electron desktop shell** for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): double-click an icon and get a standalone window running `dsh web` — no terminal, no manual commands, no browser tab juggling. **Region screenshot to ask the AI**, system tray, session manager, auto environment setup.
|
|
12
13
|
|
|
@@ -18,7 +19,7 @@ Pure shell — it **does not modify dsh itself**. The server reuses your globall
|
|
|
18
19
|
- 🗂️ **Session manager**: `Alt → File → Session Manager…` lists every session, delete with one click (permanently removes chat history)
|
|
19
20
|
- 🪟 **Standalone window**: native desktop window loading the Harness Web UI
|
|
20
21
|
- 🍱 **System tray**: closing the window minimizes to the tray; right-click to show or quit
|
|
21
|
-
- 🔄 **Easy reload**: `F5` / `Ctrl+R` / floating button —
|
|
22
|
+
- 🔄 **Easy reload**: `F5` / `Ctrl+R` / floating button — reloads the Web UI; frontend plugins (skins, layout, enhancements) take effect on reload without restarting the app
|
|
22
23
|
- 🧭 **Auto environment setup**: on first launch it detects Node.js / dsh and guides a one-click install — friendly to non-technical users
|
|
23
24
|
- 🔧 **Configurable port**: `npm start -- --port 3081` when 3080 is taken
|
|
24
25
|
- 🧠 **Smart reuse**: reuses an already-running dsh service instead of starting a duplicate
|
|
@@ -36,12 +37,15 @@ Pure shell — it **does not modify dsh itself**. The server reuses your globall
|
|
|
36
37
|
|
|
37
38
|
## 📸 Region screenshot
|
|
38
39
|
|
|
40
|
+

|
|
41
|
+
|
|
39
42
|
1. Click the **📸** button (bottom-right) — the chat window auto-hides
|
|
40
43
|
2. **Drag to select** an area on the full-screen overlay (live preview; drag the 8 handles to fine-tune after drawing)
|
|
41
44
|
3. Click **✔ OK** (or double-click / Enter) — the chat window restores
|
|
42
45
|
4. The screenshot lands in the chat box automatically → type a question → the AI answers from the image
|
|
43
46
|
|
|
44
47
|
> Pair with a vision bridge like [ModLens](https://www.npmjs.com/package/@liustack/modlens) so text-only DeepSeek models can read images too.
|
|
48
|
+
> Screenshots are also saved to `~/.dsh/screenshots/` (open the folder from the tray menu).
|
|
45
49
|
|
|
46
50
|
## Prerequisites
|
|
47
51
|
|
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
8
8
|
[]()
|
|
9
9
|
[](https://github.com/ReachGa0/dsh-desktop/actions)
|
|
10
|
+
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
11
|
|
|
11
12
|
> **English summary** · An [Electron](https://www.electronjs.org/) desktop shell for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh web) on **Windows** — double-click to run the harness in a standalone window. **Region screenshot to ask AI**, system tray, session manager, auto environment setup. Full English docs: [README.en.md](README.en.md)
|
|
12
13
|
|
|
@@ -20,7 +21,7 @@ DeepSeek Harness 的 **Electron 桌面壳**:双击图标,一个独立窗口
|
|
|
20
21
|
- 🗂️ **会话管理**:`Alt → 文件 → 会话管理…` 列出所有会话,一键删除(永久清理聊天记录)
|
|
21
22
|
- 🪟 **独立窗口**:原生桌面窗口加载 Harness Web UI,可最小化到任务栏
|
|
22
23
|
- 🍱 **系统托盘**:关窗口最小化到托盘常驻,右键菜单可随时显示/退出
|
|
23
|
-
- 🔄 **便捷刷新**:`F5` / `Ctrl+R` /
|
|
24
|
+
- 🔄 **便捷刷新**:`F5` / `Ctrl+R` / 右下角悬浮按钮,重新加载 Web UI——前端类插件(皮肤、布局、增强)装完刷新即生效,无需重启应用
|
|
24
25
|
- 🧭 **环境自动引导**:首次启动自动检测 Node.js / dsh,缺失时引导一键安装,小白也能搞定
|
|
25
26
|
- 🔧 **可换端口**:`npm start -- --port 3081` 解决端口占用
|
|
26
27
|
- 🧠 **智能复用**:检测到已有的 dsh 服务就直接复用,不重复启动
|
|
@@ -38,12 +39,15 @@ DeepSeek Harness 的 **Electron 桌面壳**:双击图标,一个独立窗口
|
|
|
38
39
|
|
|
39
40
|
## 📸 选区截图提问
|
|
40
41
|
|
|
42
|
+

|
|
43
|
+
|
|
41
44
|
1. 点右下角 **📸**(聊天窗口自动隐藏)
|
|
42
45
|
2. 全屏遮罩上**拖动框选**区域(实时显示,画完可拖 8 个手柄微调)
|
|
43
46
|
3. 点 **✔ 确定**(或双击 / 回车)→ 聊天窗口恢复
|
|
44
47
|
4. 截图自动进入聊天框 → 输入问题 → AI 看图回答
|
|
45
48
|
|
|
46
49
|
> 配合 [ModLens](https://www.npmjs.com/package/@liustack/modlens) 等识图插件,DeepSeek 文本模型也能看懂图片。
|
|
50
|
+
> 截图同时自动保存到 `~/.dsh/screenshots/`(托盘右键可打开目录)。
|
|
47
51
|
|
|
48
52
|
## 前提
|
|
49
53
|
|
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.
|
|
4
|
+
"version": "0.3.1",
|
|
5
5
|
"description": "DeepSeek Harness 桌面版(Windows)· Electron desktop shell for DeepSeek Harness — 双击即用 / double-click to run · 支持选区截图提问、系统托盘、会话管理 / region screenshot, system tray, session manager",
|
|
6
6
|
"main": "src/main.js",
|
|
7
7
|
"license": "MIT",
|
|
@@ -64,3 +64,5 @@
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
|
|
68
|
+
|
package/src/main.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const { app, BrowserWindow, dialog, shell, Tray, Menu, nativeImage, ipcMain, desktopCapturer, clipboard, screen } = require('electron')
|
|
17
17
|
const { spawn, execFile } = require('node:child_process')
|
|
18
18
|
const http = require('node:http')
|
|
19
|
+
const https = require('node:https')
|
|
19
20
|
const fs = require('node:fs')
|
|
20
21
|
const path = require('node:path')
|
|
21
22
|
const os = require('node:os')
|
|
@@ -62,6 +63,103 @@ function shotLog(...args) {
|
|
|
62
63
|
} catch { /* 忽略日志错误 */ }
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
/* ------------------------------------------------------------------ *
|
|
67
|
+
* 自动更新检查(启动时异步检测 GitHub Releases 最新版)
|
|
68
|
+
* ------------------------------------------------------------------ */
|
|
69
|
+
|
|
70
|
+
const REPO = 'ReachGa0/dsh-desktop'
|
|
71
|
+
const RELEASES_URL = `https://api.github.com/repos/${REPO}/releases/latest`
|
|
72
|
+
const RELEASES_PAGE = `https://github.com/${REPO}/releases/latest`
|
|
73
|
+
const UPDATE_CHECK_TIMEOUT_MS = 10_000
|
|
74
|
+
|
|
75
|
+
/** GET 一个 JSON(带超时与 UA,GitHub API 要求 UA)。resolve null 表示失败。 */
|
|
76
|
+
function fetchJson(url) {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
// 首次用系统证书;代理/反代环境证书校验可能失败,降级为不校验重试一次
|
|
79
|
+
// (仅更新检查用,不降低应用其他部分的安全级别)
|
|
80
|
+
const attempt = (rejectUnauthorized) => {
|
|
81
|
+
const req = https.get(
|
|
82
|
+
url,
|
|
83
|
+
{ headers: { 'User-Agent': 'dsh-desktop-update-check', Accept: 'application/vnd.github+json' }, timeout: UPDATE_CHECK_TIMEOUT_MS, rejectUnauthorized },
|
|
84
|
+
(res) => {
|
|
85
|
+
if (res.statusCode !== 200) {
|
|
86
|
+
res.resume()
|
|
87
|
+
resolve(null)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
let body = ''
|
|
91
|
+
res.setEncoding('utf8')
|
|
92
|
+
res.on('data', (d) => (body += d))
|
|
93
|
+
res.on('end', () => {
|
|
94
|
+
try { resolve(JSON.parse(body)) } catch { resolve(null) }
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
req.on('timeout', () => req.destroy())
|
|
99
|
+
req.on('error', () => {
|
|
100
|
+
// 证书类错误:再试一次宽松模式;其他错误直接放弃
|
|
101
|
+
if (rejectUnauthorized) attempt(false)
|
|
102
|
+
else resolve(null)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
attempt(true)
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 解析语义化版本号 → [major, minor, patch],失败返回 null。 */
|
|
110
|
+
function parseVersion(v) {
|
|
111
|
+
if (typeof v !== 'string') return null
|
|
112
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim())
|
|
113
|
+
if (!m) return null
|
|
114
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** a 比 b 新?都解析失败时返回 false(宁可漏报不可误报)。 */
|
|
118
|
+
function isNewer(a, b) {
|
|
119
|
+
const pa = parseVersion(a)
|
|
120
|
+
const pb = parseVersion(b)
|
|
121
|
+
if (!pa || !pb) return false
|
|
122
|
+
for (let i = 0; i < 3; i++) {
|
|
123
|
+
if (pa[i] !== pb[i]) return pa[i] > pb[i]
|
|
124
|
+
}
|
|
125
|
+
return false
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 检查更新:查询 GitHub Releases 最新版,若比当前版本新则弹窗提示。
|
|
130
|
+
* 全程静默失败(网络不通 / 解析失败 / 无新版都不打扰用户)。
|
|
131
|
+
*/
|
|
132
|
+
async function checkForUpdates() {
|
|
133
|
+
const current = app.getVersion()
|
|
134
|
+
try {
|
|
135
|
+
const release = await fetchJson(RELEASES_URL)
|
|
136
|
+
if (!release) {
|
|
137
|
+
log('update check: no release data (offline or API error)')
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
const latest = release.tag_name
|
|
141
|
+
log(`update check: current=${current} latest=${latest}`)
|
|
142
|
+
if (!isNewer(latest, current)) return
|
|
143
|
+
const name = release.name && release.name !== latest ? `「${release.name}」` : ''
|
|
144
|
+
const detail = (release.body || '').split('\n').slice(0, 6).join('\n').trim()
|
|
145
|
+
const { response } = await dialog.showMessageBox(mainWindow || undefined, {
|
|
146
|
+
type: 'info',
|
|
147
|
+
title: '发现新版本',
|
|
148
|
+
message: `DeepSeek Harness Desktop 有新版本可用(${latest} ${name})`,
|
|
149
|
+
detail: detail ? `更新内容:\n${detail}\n\n当前版本:${current}` : `当前版本:${current}`,
|
|
150
|
+
buttons: ['前往下载', '以后再说'],
|
|
151
|
+
defaultId: 0,
|
|
152
|
+
cancelId: 1,
|
|
153
|
+
noLink: true,
|
|
154
|
+
})
|
|
155
|
+
if (response === 0) {
|
|
156
|
+
shell.openExternal(RELEASES_PAGE)
|
|
157
|
+
}
|
|
158
|
+
} catch (e) {
|
|
159
|
+
log('update check failed:', e && e.message)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
65
163
|
/* ------------------------------------------------------------------ *
|
|
66
164
|
* 工具:探测 / 等待 HTTP 服务
|
|
67
165
|
* ------------------------------------------------------------------ */
|
|
@@ -105,13 +203,19 @@ function resolveDshBin() {
|
|
|
105
203
|
return 'dsh.cmd'
|
|
106
204
|
}
|
|
107
205
|
|
|
206
|
+
// 主动关停 dsh 时的标记(杀进程也会触发 exit,需区分「主动关」和「意外崩」)
|
|
207
|
+
let isShuttingDownDsh = false
|
|
208
|
+
// 已自动重启过一次的标志(同一次会话最多自愈一次,避免崩溃循环)
|
|
209
|
+
let didAutoRestartDsh = false
|
|
210
|
+
|
|
108
211
|
/** 启动 dsh web,日志追加到 userData/dsh.log。返回子进程。 */
|
|
109
212
|
function startDsh(port) {
|
|
110
213
|
const bin = resolveDshBin()
|
|
111
214
|
const logFile = path.join(app.getPath('userData'), 'dsh.log')
|
|
112
215
|
const out = fs.openSync(logFile, 'a')
|
|
113
216
|
// shell:true 时把参数拼进命令字符串(参数均为常量),避免 DEP0190 警告
|
|
114
|
-
|
|
217
|
+
// --no-open 关闭 dsh 默认的自动打开浏览器行为(桌面壳自己用 BrowserWindow 加载,不弹浏览器)
|
|
218
|
+
const cmd = `"${bin}" web --port ${port} --no-open`
|
|
115
219
|
const child = spawn(cmd, {
|
|
116
220
|
shell: true,
|
|
117
221
|
windowsHide: true,
|
|
@@ -122,7 +226,12 @@ function startDsh(port) {
|
|
|
122
226
|
log(`dsh log: ${logFile}`)
|
|
123
227
|
child.on('exit', (code, signal) => {
|
|
124
228
|
log(`dsh exited: code=${code} signal=${signal}`)
|
|
125
|
-
if (ownedDsh
|
|
229
|
+
if (ownedDsh !== child) return
|
|
230
|
+
ownedDsh = null
|
|
231
|
+
// 崩溃自愈:本壳启动的 dsh 意外退出(非主动关闭/非退出中)时自动重启一次
|
|
232
|
+
if (!isQuitting && !isShuttingDownDsh) {
|
|
233
|
+
restartOwnedDsh(port, code)
|
|
234
|
+
}
|
|
126
235
|
})
|
|
127
236
|
child.on('error', (err) => {
|
|
128
237
|
log(`dsh spawn error: ${err.message}`)
|
|
@@ -130,6 +239,52 @@ function startDsh(port) {
|
|
|
130
239
|
return child
|
|
131
240
|
}
|
|
132
241
|
|
|
242
|
+
/** dsh 意外退出后自动重启一次;二次退出则提示用户并放弃。 */
|
|
243
|
+
function restartOwnedDsh(port, exitCode) {
|
|
244
|
+
if (didAutoRestartDsh) {
|
|
245
|
+
log('dsh crashed again after auto-restart; giving up')
|
|
246
|
+
const logFile = path.join(app.getPath('userData'), 'dsh.log')
|
|
247
|
+
dialog.showMessageBox(mainWindow || undefined, {
|
|
248
|
+
type: 'error',
|
|
249
|
+
title: 'dsh 服务异常',
|
|
250
|
+
message: 'dsh 服务启动后再次退出,已停止自动重启。',
|
|
251
|
+
detail: `退出码:${exitCode ?? '未知'}\n\n日志文件:${logFile}\n\n请查看日志定位问题,或手动在终端运行 dsh web 排查。`,
|
|
252
|
+
buttons: ['打开日志目录', '知道了'],
|
|
253
|
+
noLink: true,
|
|
254
|
+
}).then(({ response }) => {
|
|
255
|
+
if (response === 0) shell.showItemInFolder(logFile)
|
|
256
|
+
})
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
log(`dsh exited unexpectedly (code=${exitCode}); restarting in 3s…`)
|
|
260
|
+
didAutoRestartDsh = true
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
if (isQuitting) return
|
|
263
|
+
// 重启前再确认端口没有被外部服务接管(避免重复启动)
|
|
264
|
+
probe(port).then((up) => {
|
|
265
|
+
if (up) {
|
|
266
|
+
log('port already serving after crash; not restarting')
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
const child = startDsh(port)
|
|
270
|
+
ownedDsh = child
|
|
271
|
+
// 等它就绪;若起不来则等 exit 回调里的二次提示
|
|
272
|
+
waitForServer(port, START_TIMEOUT_MS).then((ready) => {
|
|
273
|
+
if (ready) {
|
|
274
|
+
log('dsh auto-restarted successfully')
|
|
275
|
+
dialog.showMessageBox(mainWindow || undefined, {
|
|
276
|
+
type: 'info',
|
|
277
|
+
title: 'dsh 已自动重启',
|
|
278
|
+
message: 'dsh 服务意外退出,已自动恢复。',
|
|
279
|
+
buttons: ['好'],
|
|
280
|
+
noLink: true,
|
|
281
|
+
}).catch(() => {})
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
}, 3000)
|
|
286
|
+
}
|
|
287
|
+
|
|
133
288
|
/** 杀掉进程树(Windows 用 taskkill /T 覆盖子孙进程),带超时兜底。 */
|
|
134
289
|
function killProcessTree(pid) {
|
|
135
290
|
return new Promise((resolve) => {
|
|
@@ -147,11 +302,64 @@ async function shutdownOwnedDsh() {
|
|
|
147
302
|
if (ownedDsh && ownedDsh.pid) {
|
|
148
303
|
const pid = ownedDsh.pid
|
|
149
304
|
ownedDsh = null
|
|
305
|
+
isShuttingDownDsh = true
|
|
150
306
|
await killProcessTree(pid)
|
|
151
307
|
log(`killed owned dsh pid ${pid}`)
|
|
152
308
|
}
|
|
153
309
|
}
|
|
154
310
|
|
|
311
|
+
/**
|
|
312
|
+
* 重启 dsh 服务:杀掉当前由本壳启动的 dsh → 重新拉起 → 等就绪 → 刷新页面。
|
|
313
|
+
* 供「重启 dsh 服务」菜单/托盘项使用(后端插件/服务改动后需要)。
|
|
314
|
+
*/
|
|
315
|
+
async function restartDshService() {
|
|
316
|
+
if (!ownedDsh) {
|
|
317
|
+
log('restart: no owned dsh to restart')
|
|
318
|
+
// 没有自管的 dsh(可能复用了外部服务)→ 无法安全重启,提示用户
|
|
319
|
+
dialog.showMessageBox(mainWindow || undefined, {
|
|
320
|
+
type: 'info',
|
|
321
|
+
title: '无法重启',
|
|
322
|
+
message: '当前使用的是外部已有的 dsh 服务,不是本应用启动的,无法从这里重启。',
|
|
323
|
+
detail: '请在外部终端手动重启该服务,然后刷新页面(F5)。',
|
|
324
|
+
buttons: ['好'],
|
|
325
|
+
noLink: true,
|
|
326
|
+
})
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
const port = resolvePort()
|
|
330
|
+
log(`restart: restarting dsh service on port ${port}…`)
|
|
331
|
+
// 先停(标记主动关停,避免触发崩溃自愈逻辑)
|
|
332
|
+
await shutdownOwnedDsh()
|
|
333
|
+
isShuttingDownDsh = false // 重置,让后续退出能触发自愈
|
|
334
|
+
// 重新启动
|
|
335
|
+
ownedDsh = startDsh(port)
|
|
336
|
+
const ready = await waitForServer(port, START_TIMEOUT_MS)
|
|
337
|
+
if (!ready) {
|
|
338
|
+
log('restart: dsh failed to come back')
|
|
339
|
+
dialog.showMessageBox(mainWindow || undefined, {
|
|
340
|
+
type: 'error',
|
|
341
|
+
title: '重启失败',
|
|
342
|
+
message: 'dsh 服务重启后未在预期时间内就绪。',
|
|
343
|
+
detail: `日志文件:${path.join(app.getPath('userData'), 'dsh.log')}`,
|
|
344
|
+
buttons: ['好'],
|
|
345
|
+
noLink: true,
|
|
346
|
+
})
|
|
347
|
+
return
|
|
348
|
+
}
|
|
349
|
+
log('restart: dsh back up; reloading window')
|
|
350
|
+
// 等页面也能加载后刷新
|
|
351
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
352
|
+
mainWindow.loadURL(`http://127.0.0.1:${port}`)
|
|
353
|
+
}
|
|
354
|
+
dialog.showMessageBox(mainWindow || undefined, {
|
|
355
|
+
type: 'info',
|
|
356
|
+
title: 'dsh 服务已重启',
|
|
357
|
+
message: 'dsh 服务已重启完成,页面已刷新。',
|
|
358
|
+
buttons: ['好'],
|
|
359
|
+
noLink: true,
|
|
360
|
+
}).catch(() => {})
|
|
361
|
+
}
|
|
362
|
+
|
|
155
363
|
/* ------------------------------------------------------------------ *
|
|
156
364
|
* 会话管理(读取/删除 ~/.dsh/sessions 下的会话)
|
|
157
365
|
* ------------------------------------------------------------------ */
|
|
@@ -487,12 +695,15 @@ function onShotDone(rect) {
|
|
|
487
695
|
}
|
|
488
696
|
const cropped = image.crop(r)
|
|
489
697
|
shotLog('shot: cropped', cropped.getSize().width + 'x' + cropped.getSize().height)
|
|
490
|
-
//
|
|
698
|
+
// 保存到 ~/.dsh/screenshots/(持久保存,方便回顾;同时写剪贴板)
|
|
491
699
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
700
|
+
const dir = path.join(os.homedir(), '.dsh', 'screenshots')
|
|
701
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
702
|
+
const file = path.join(dir, `shot-${new Date().toISOString().replace(/[:.]/g, '-')}.png`)
|
|
703
|
+
fs.writeFileSync(file, cropped.toPNG())
|
|
704
|
+
shotLog('shot: saved', file)
|
|
494
705
|
} catch (e) {
|
|
495
|
-
shotLog('shot:
|
|
706
|
+
shotLog('shot: save failed', String(e && e.message || e))
|
|
496
707
|
}
|
|
497
708
|
clipboard.writeImage(cropped)
|
|
498
709
|
shotLog('shot: written to clipboard')
|
|
@@ -539,6 +750,19 @@ function createTray() {
|
|
|
539
750
|
Menu.buildFromTemplate([
|
|
540
751
|
{ label: '显示主窗口', click: showMainWindow },
|
|
541
752
|
{ type: 'separator' },
|
|
753
|
+
{
|
|
754
|
+
label: '打开截图目录',
|
|
755
|
+
click: () => {
|
|
756
|
+
const dir = path.join(os.homedir(), '.dsh', 'screenshots')
|
|
757
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
758
|
+
shell.openPath(dir)
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
label: '重启 dsh 服务',
|
|
763
|
+
click: () => { restartDshService() },
|
|
764
|
+
},
|
|
765
|
+
{ type: 'separator' },
|
|
542
766
|
{
|
|
543
767
|
label: '退出',
|
|
544
768
|
click: () => {
|
|
@@ -570,6 +794,10 @@ function createApplicationMenu() {
|
|
|
570
794
|
label: '会话管理…',
|
|
571
795
|
click: () => openSessionsWindow(),
|
|
572
796
|
},
|
|
797
|
+
{
|
|
798
|
+
label: '重启 dsh 服务…',
|
|
799
|
+
click: () => restartDshService(),
|
|
800
|
+
},
|
|
573
801
|
{ type: 'separator' },
|
|
574
802
|
{
|
|
575
803
|
label: '退出',
|
|
@@ -675,6 +903,8 @@ if (!gotLock) {
|
|
|
675
903
|
createWindow(`http://127.0.0.1:${port}`)
|
|
676
904
|
createTray()
|
|
677
905
|
createApplicationMenu()
|
|
906
|
+
// 自动更新检查:不阻塞启动,静默失败
|
|
907
|
+
checkForUpdates()
|
|
678
908
|
// preload 注入的悬浮刷新按钮 → 重新加载页面
|
|
679
909
|
ipcMain.on('dsh-desktop:reload', () => {
|
|
680
910
|
if (mainWindow) mainWindow.webContents.reload()
|