dsh-windows-tray 1.0.2 → 1.0.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/bin/dsh-tray.js +0 -1
- package/build/build-tray-icons.ps1 +98 -98
- package/docs/system-tray-guide.md +133 -133
- package/install.ps1 +228 -184
- package/launcher/dsh-actions.ps1 +50 -50
- package/launcher/dsh-control.ps1 +40 -40
- package/launcher/dsh-lib.ps1 +50 -11
- package/launcher/dsh-menu.ps1 +93 -93
- package/launcher/dsh-tray.bat +10 -10
- package/launcher/dsh-tray.ps1 +429 -418
- package/launcher/dsh-update.ps1 +115 -115
- package/package.json +39 -36
- package/launcher/bh-menu.bat +0 -4
- package/launcher/bh-menu.ps1 +0 -56
- package/launcher/dsh-all-menu.bat +0 -4
- package/launcher/dsh-all-menu.ps1 +0 -106
package/bin/dsh-tray.js
CHANGED
|
@@ -19,7 +19,6 @@ function findPwsh() {
|
|
|
19
19
|
const candidates = [
|
|
20
20
|
process.env.PWSH,
|
|
21
21
|
'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
|
|
22
|
-
'D:\\Program Files (x86)\\PowerShell-7.6.4\\pwsh.exe',
|
|
23
22
|
path.join(process.env.ProgramW6432 || 'C:\\Program Files', 'PowerShell\\7\\pwsh.exe'),
|
|
24
23
|
].filter(Boolean);
|
|
25
24
|
for (const c of candidates) { try { if (fs.existsSync(c)) return c; } catch (_) { /* ignore */ } }
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# build-tray-icons.ps1 —— 从官方鲸鱼 favicon.svg 生成系统托盘图标 (.ico)
|
|
2
|
-
# 用法: pwsh -NoProfile -File build-tray-icons.ps1 [-OutDir <dir>]
|
|
3
|
-
# 输入: whale-render.html 的截图 whale-512.png (浏览器渲染, 黑鲸/白底)
|
|
4
|
-
# 输出: dsh-tray-running.ico (品牌蓝 #4176e6) / dsh-tray-stopped.ico (灰 #81858c)
|
|
5
|
-
# 算法: 灰度 -> 透明度 (白=全透明, 黑=不透明), 整体填充目标色, 保留抗锯齿边缘
|
|
6
|
-
param(
|
|
7
|
-
[string]$OutDir = (Join-Path $PSScriptRoot 'icons')
|
|
8
|
-
)
|
|
9
|
-
$ErrorActionPreference = 'Stop'
|
|
10
|
-
Add-Type -AssemblyName System.Drawing
|
|
11
|
-
|
|
12
|
-
$srcPath = Join-Path $PSScriptRoot 'whale-512.png'
|
|
13
|
-
if (-not (Test-Path $srcPath)) { throw "missing $srcPath (先用浏览器打开 whale-render.html 并截图保存) " }
|
|
14
|
-
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
|
15
|
-
|
|
16
|
-
# 1) 裁出左上角 512x512
|
|
17
|
-
$raw = New-Object System.Drawing.Bitmap($srcPath)
|
|
18
|
-
$crop = New-Object System.Drawing.Bitmap 512, 512, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
19
|
-
$g = [System.Drawing.Graphics]::FromImage($crop)
|
|
20
|
-
$g.DrawImage($raw, (New-Object System.Drawing.Rectangle(0, 0, 512, 512)), (New-Object System.Drawing.Rectangle(0, 0, 512, 512)), [System.Drawing.GraphicsUnit]::Pixel)
|
|
21
|
-
$g.Dispose(); $raw.Dispose()
|
|
22
|
-
|
|
23
|
-
# 2) 灰度 -> 透明度 + 目标色填充 (LockBits 逐像素, 512x512 约 1~2 秒)
|
|
24
|
-
function Convert-GrayToTint([System.Drawing.Bitmap]$src, [int]$tr, [int]$tg, [int]$tb) {
|
|
25
|
-
$rect = New-Object System.Drawing.Rectangle(0, 0, $src.Width, $src.Height)
|
|
26
|
-
$data = $src.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
27
|
-
$bytes = New-Object byte[] ($data.Stride * $src.Height)
|
|
28
|
-
[System.Runtime.InteropServices.Marshal]::Copy($data.Scan0, $bytes, 0, $bytes.Length)
|
|
29
|
-
$src.UnlockBits($data)
|
|
30
|
-
$out = New-Object System.Drawing.Bitmap $src.Width, $src.Height, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
31
|
-
$outData = $out.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::WriteOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
32
|
-
$outBytes = New-Object byte[] ($outData.Stride * $out.Height)
|
|
33
|
-
for ($y = 0; $y -lt $src.Height; $y++) {
|
|
34
|
-
$row = $y * $data.Stride
|
|
35
|
-
$oRow = $y * $outData.Stride
|
|
36
|
-
for ($x = 0; $x -lt $src.Width; $x++) {
|
|
37
|
-
$i = $row + $x * 4
|
|
38
|
-
$lum = [int](($bytes[$i] + $bytes[$i + 1] + $bytes[$i + 2]) / 3)
|
|
39
|
-
$a = 255 - $lum
|
|
40
|
-
$j = $oRow + $x * 4
|
|
41
|
-
$outBytes[$j] = [byte]$tb
|
|
42
|
-
$outBytes[$j + 1] = [byte]$tg
|
|
43
|
-
$outBytes[$j + 2] = [byte]$tr
|
|
44
|
-
$outBytes[$j + 3] = [byte]$a
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
[System.Runtime.InteropServices.Marshal]::Copy($outBytes, 0, $outData.Scan0, $outBytes.Length)
|
|
48
|
-
$out.UnlockBits($outData)
|
|
49
|
-
return $out
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# 3) 多尺寸 ICO (内嵌 PNG)
|
|
53
|
-
function Write-TrayIco([string]$path, [System.Drawing.Bitmap]$tint512, [int[]]$sizes) {
|
|
54
|
-
$blobs = @()
|
|
55
|
-
foreach ($s in $sizes) {
|
|
56
|
-
$bmp = New-Object System.Drawing.Bitmap $s, $s, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
57
|
-
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
58
|
-
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
59
|
-
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
60
|
-
$g.DrawImage($tint512, 0, 0, $s, $s)
|
|
61
|
-
$g.Dispose()
|
|
62
|
-
$ms = New-Object System.IO.MemoryStream
|
|
63
|
-
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
64
|
-
$blobs += , $ms.ToArray()
|
|
65
|
-
$ms.Dispose(); $bmp.Dispose()
|
|
66
|
-
}
|
|
67
|
-
$fs = [System.IO.File]::Create($path)
|
|
68
|
-
$bw = New-Object System.IO.BinaryWriter($fs)
|
|
69
|
-
$bw.Write([uint16]0); $bw.Write([uint16]1); $bw.Write([uint16]$sizes.Count)
|
|
70
|
-
$offset = 6 + 16 * $sizes.Count
|
|
71
|
-
for ($i = 0; $i -lt $sizes.Count; $i++) {
|
|
72
|
-
$s = $sizes[$i]
|
|
73
|
-
$dim = if ($s -ge 256) { 0 } else { $s }
|
|
74
|
-
$bw.Write([byte]$dim); $bw.Write([byte]$dim)
|
|
75
|
-
$bw.Write([byte]0); $bw.Write([byte]0)
|
|
76
|
-
$bw.Write([uint16]1); $bw.Write([uint16]32)
|
|
77
|
-
$bw.Write([uint32]$blobs[$i].Length); $bw.Write([uint32]$offset)
|
|
78
|
-
$offset += $blobs[$i].Length
|
|
79
|
-
}
|
|
80
|
-
foreach ($b in $blobs) { $bw.Write($b) }
|
|
81
|
-
$bw.Close(); $fs.Close()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# 品牌蓝 = DSH 主题 deepseek-500 #4176e6 ; 停止灰 = neutral-bluish-600 #81858c
|
|
85
|
-
$blue = Convert-GrayToTint $crop 0x41 0x76 0xE6
|
|
86
|
-
$gray = Convert-GrayToTint $crop 0x81 0x85 0x8C
|
|
87
|
-
$sizes = @(16, 24, 32, 48, 64, 256)
|
|
88
|
-
|
|
89
|
-
Write-TrayIco (Join-Path $OutDir 'dsh-tray-running.ico') $blue $sizes
|
|
90
|
-
Write-TrayIco (Join-Path $OutDir 'dsh-tray-stopped.ico') $gray $sizes
|
|
91
|
-
|
|
92
|
-
# 256px PNG 预览
|
|
93
|
-
$blue.Save((Join-Path $OutDir 'dsh-tray-running-preview.png'), [System.Drawing.Imaging.ImageFormat]::Png)
|
|
94
|
-
$gray.Save((Join-Path $OutDir 'dsh-tray-stopped-preview.png'), [System.Drawing.Imaging.ImageFormat]::Png)
|
|
95
|
-
|
|
96
|
-
$crop.Dispose(); $blue.Dispose(); $gray.Dispose()
|
|
97
|
-
Get-ChildItem $OutDir | Select-Object Name, Length | Format-Table -AutoSize
|
|
98
|
-
Write-Host "icons written to $OutDir"
|
|
1
|
+
# build-tray-icons.ps1 —— 从官方鲸鱼 favicon.svg 生成系统托盘图标 (.ico)
|
|
2
|
+
# 用法: pwsh -NoProfile -File build-tray-icons.ps1 [-OutDir <dir>]
|
|
3
|
+
# 输入: whale-render.html 的截图 whale-512.png (浏览器渲染, 黑鲸/白底)
|
|
4
|
+
# 输出: dsh-tray-running.ico (品牌蓝 #4176e6) / dsh-tray-stopped.ico (灰 #81858c)
|
|
5
|
+
# 算法: 灰度 -> 透明度 (白=全透明, 黑=不透明), 整体填充目标色, 保留抗锯齿边缘
|
|
6
|
+
param(
|
|
7
|
+
[string]$OutDir = (Join-Path $PSScriptRoot 'icons')
|
|
8
|
+
)
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
Add-Type -AssemblyName System.Drawing
|
|
11
|
+
|
|
12
|
+
$srcPath = Join-Path $PSScriptRoot 'whale-512.png'
|
|
13
|
+
if (-not (Test-Path $srcPath)) { throw "missing $srcPath (先用浏览器打开 whale-render.html 并截图保存) " }
|
|
14
|
+
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
|
|
15
|
+
|
|
16
|
+
# 1) 裁出左上角 512x512
|
|
17
|
+
$raw = New-Object System.Drawing.Bitmap($srcPath)
|
|
18
|
+
$crop = New-Object System.Drawing.Bitmap 512, 512, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
19
|
+
$g = [System.Drawing.Graphics]::FromImage($crop)
|
|
20
|
+
$g.DrawImage($raw, (New-Object System.Drawing.Rectangle(0, 0, 512, 512)), (New-Object System.Drawing.Rectangle(0, 0, 512, 512)), [System.Drawing.GraphicsUnit]::Pixel)
|
|
21
|
+
$g.Dispose(); $raw.Dispose()
|
|
22
|
+
|
|
23
|
+
# 2) 灰度 -> 透明度 + 目标色填充 (LockBits 逐像素, 512x512 约 1~2 秒)
|
|
24
|
+
function Convert-GrayToTint([System.Drawing.Bitmap]$src, [int]$tr, [int]$tg, [int]$tb) {
|
|
25
|
+
$rect = New-Object System.Drawing.Rectangle(0, 0, $src.Width, $src.Height)
|
|
26
|
+
$data = $src.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::ReadOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
27
|
+
$bytes = New-Object byte[] ($data.Stride * $src.Height)
|
|
28
|
+
[System.Runtime.InteropServices.Marshal]::Copy($data.Scan0, $bytes, 0, $bytes.Length)
|
|
29
|
+
$src.UnlockBits($data)
|
|
30
|
+
$out = New-Object System.Drawing.Bitmap $src.Width, $src.Height, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
31
|
+
$outData = $out.LockBits($rect, [System.Drawing.Imaging.ImageLockMode]::WriteOnly, [System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
32
|
+
$outBytes = New-Object byte[] ($outData.Stride * $out.Height)
|
|
33
|
+
for ($y = 0; $y -lt $src.Height; $y++) {
|
|
34
|
+
$row = $y * $data.Stride
|
|
35
|
+
$oRow = $y * $outData.Stride
|
|
36
|
+
for ($x = 0; $x -lt $src.Width; $x++) {
|
|
37
|
+
$i = $row + $x * 4
|
|
38
|
+
$lum = [int](($bytes[$i] + $bytes[$i + 1] + $bytes[$i + 2]) / 3)
|
|
39
|
+
$a = 255 - $lum
|
|
40
|
+
$j = $oRow + $x * 4
|
|
41
|
+
$outBytes[$j] = [byte]$tb
|
|
42
|
+
$outBytes[$j + 1] = [byte]$tg
|
|
43
|
+
$outBytes[$j + 2] = [byte]$tr
|
|
44
|
+
$outBytes[$j + 3] = [byte]$a
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
[System.Runtime.InteropServices.Marshal]::Copy($outBytes, 0, $outData.Scan0, $outBytes.Length)
|
|
48
|
+
$out.UnlockBits($outData)
|
|
49
|
+
return $out
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# 3) 多尺寸 ICO (内嵌 PNG)
|
|
53
|
+
function Write-TrayIco([string]$path, [System.Drawing.Bitmap]$tint512, [int[]]$sizes) {
|
|
54
|
+
$blobs = @()
|
|
55
|
+
foreach ($s in $sizes) {
|
|
56
|
+
$bmp = New-Object System.Drawing.Bitmap $s, $s, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
|
|
57
|
+
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
58
|
+
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
59
|
+
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
|
|
60
|
+
$g.DrawImage($tint512, 0, 0, $s, $s)
|
|
61
|
+
$g.Dispose()
|
|
62
|
+
$ms = New-Object System.IO.MemoryStream
|
|
63
|
+
$bmp.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
64
|
+
$blobs += , $ms.ToArray()
|
|
65
|
+
$ms.Dispose(); $bmp.Dispose()
|
|
66
|
+
}
|
|
67
|
+
$fs = [System.IO.File]::Create($path)
|
|
68
|
+
$bw = New-Object System.IO.BinaryWriter($fs)
|
|
69
|
+
$bw.Write([uint16]0); $bw.Write([uint16]1); $bw.Write([uint16]$sizes.Count)
|
|
70
|
+
$offset = 6 + 16 * $sizes.Count
|
|
71
|
+
for ($i = 0; $i -lt $sizes.Count; $i++) {
|
|
72
|
+
$s = $sizes[$i]
|
|
73
|
+
$dim = if ($s -ge 256) { 0 } else { $s }
|
|
74
|
+
$bw.Write([byte]$dim); $bw.Write([byte]$dim)
|
|
75
|
+
$bw.Write([byte]0); $bw.Write([byte]0)
|
|
76
|
+
$bw.Write([uint16]1); $bw.Write([uint16]32)
|
|
77
|
+
$bw.Write([uint32]$blobs[$i].Length); $bw.Write([uint32]$offset)
|
|
78
|
+
$offset += $blobs[$i].Length
|
|
79
|
+
}
|
|
80
|
+
foreach ($b in $blobs) { $bw.Write($b) }
|
|
81
|
+
$bw.Close(); $fs.Close()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# 品牌蓝 = DSH 主题 deepseek-500 #4176e6 ; 停止灰 = neutral-bluish-600 #81858c
|
|
85
|
+
$blue = Convert-GrayToTint $crop 0x41 0x76 0xE6
|
|
86
|
+
$gray = Convert-GrayToTint $crop 0x81 0x85 0x8C
|
|
87
|
+
$sizes = @(16, 24, 32, 48, 64, 256)
|
|
88
|
+
|
|
89
|
+
Write-TrayIco (Join-Path $OutDir 'dsh-tray-running.ico') $blue $sizes
|
|
90
|
+
Write-TrayIco (Join-Path $OutDir 'dsh-tray-stopped.ico') $gray $sizes
|
|
91
|
+
|
|
92
|
+
# 256px PNG 预览
|
|
93
|
+
$blue.Save((Join-Path $OutDir 'dsh-tray-running-preview.png'), [System.Drawing.Imaging.ImageFormat]::Png)
|
|
94
|
+
$gray.Save((Join-Path $OutDir 'dsh-tray-stopped-preview.png'), [System.Drawing.Imaging.ImageFormat]::Png)
|
|
95
|
+
|
|
96
|
+
$crop.Dispose(); $blue.Dispose(); $gray.Dispose()
|
|
97
|
+
Get-ChildItem $OutDir | Select-Object Name, Length | Format-Table -AutoSize
|
|
98
|
+
Write-Host "icons written to $OutDir"
|
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
# DSH 系统托盘(Windows)—— npm 包使用手册
|
|
2
|
-
|
|
3
|
-
> 包名: **`dsh-windows-tray`** · 部署位置: `~\.dsh\launcher` · 技术栈: PowerShell 7 + WinForms,零 npm 依赖、纯旁观者、不改 dsh 源码。
|
|
4
|
-
> 安装 / 更新 / 卸载全部走 npm 标准流程;开发归档见 [tray-dev-archive.md](tray-dev-archive.md),相关踩坑见 [powershell-pitfalls.md](powershell-pitfalls.md)。
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 一、安装与部署
|
|
9
|
-
|
|
10
|
-
前置:Windows 10/11 + [PowerShell 7](https://github.com/PowerShell/PowerShell/releases)(7.x;缺失时自动回退 Windows PowerShell 5.1)+ Node.js **≥ 18** + 已安装 dsh(任意位置)。
|
|
11
|
-
|
|
12
|
-
```powershell
|
|
13
|
-
# 1. 全局安装
|
|
14
|
-
npm i -g dsh-windows-tray
|
|
15
|
-
|
|
16
|
-
# 2. 一键部署:自动探测 dsh/pwsh/node 路径 → 安装到 ~\.dsh\launcher
|
|
17
|
-
# → 按本机路径改写配置 → 创建开机自启 + 桌面快捷方式 → 启动托盘
|
|
18
|
-
dsh-tray install
|
|
19
|
-
|
|
20
|
-
# 3. 首次使用:把任务栏 ^ 溢出区里的鲸鱼拖到任务栏(一次性,Windows 会记住)
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### 安装后必做:本机参数探测 → 确认 → 回填(不要信默认值)
|
|
24
|
-
|
|
25
|
-
install 只自动改写 dsh/pwsh/node **路径**;**端口、数据目录按包默认值写入且不改**。与本机实际不一致时,症状是托盘永远灰鲸(探测错端口)或托盘/控制台启动的 web 落到空数据目录。先实测,人工确认数值,再回填到 §六 列出的**全部**填充点(只改一处会被另一处覆盖):
|
|
26
|
-
|
|
27
|
-
```powershell
|
|
28
|
-
# 实际 Web 端口:看运行中的 web 进程命令行(没在跑就看本机控制台/启动脚本里的启动命令)
|
|
29
|
-
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
|
|
30
|
-
Where-Object { $_.CommandLine -like '*web*' } |
|
|
31
|
-
ForEach-Object { $_.CommandLine } # 输出里找 --port NNNN 与 --host
|
|
32
|
-
|
|
33
|
-
# 实际数据目录:找带 settings.yaml + skins/ + profiles/ 的目录(默认 ~\.dsh,自定义部署不在那)
|
|
34
|
-
Test-Path "$env:USERPROFILE\.dsh\settings.yaml" # True=默认目录;False=自定义 DSH_HOME,必须回填
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
常用命令:
|
|
38
|
-
|
|
39
|
-
| 命令 | 作用 |
|
|
40
|
-
| :--- | :--- |
|
|
41
|
-
| `dsh-tray` | 启动托盘(已有实例则提示,不重复启动) |
|
|
42
|
-
| `dsh-tray install` | 一键部署 / 重新部署(可透传 `-DshRoot` 等参数) |
|
|
43
|
-
| `dsh-tray icons` | 重新生成鲸鱼图标(内置 512px 源图,无需浏览器) |
|
|
44
|
-
| `dsh-tray info` | 查看版本 / 安装位置 / 部署状态 |
|
|
45
|
-
|
|
46
|
-
## 二、功能一览
|
|
47
|
-
|
|
48
|
-
| 交互 | 行为 |
|
|
49
|
-
| :--- | :--- |
|
|
50
|
-
| **图标** | 🐋 品牌蓝鲸鱼 = Web 运行中;灰鲸鱼 = 未运行(官方 favicon 渲染,多尺寸 16~256px) |
|
|
51
|
-
| **悬停提示** | `Web UI 运行中 · 127.0.0.1:3088` / `Web UI 未运行 · 右键可启动` |
|
|
52
|
-
| **双击** | 打开 Web UI(未运行时提示先启动) |
|
|
53
|
-
| **右键菜单** | 顶部 `Web UI 状态` / `TUI 状态` 双状态行(含 PID);`Web UI ▸`(打开 Web UI / 启动 / 停止(带确认) / 重启);`TUI ▸`(启动 / 停止);打开日志目录 / `版本信息 ▸`(版本 / 检查更新 / 更新到最新版)/ 退出托盘 |
|
|
54
|
-
| **菜单智能禁用** | Web 运行中禁用「启动 Web UI」;TUI 运行中禁用「启动 TUI」 |
|
|
55
|
-
| **状态通知** | Web/TUI 启动与停止瞬间弹系统通知;托盘就绪弹一次提示 |
|
|
56
|
-
| **开机自启** | 登录后托盘自动出现(启动文件夹快捷方式,安装时自动创建) |
|
|
57
|
-
|
|
58
|
-
托盘每 2 秒探测一次 `127.0.0.1:3088`(TCP 连接,500ms 超时,开销可忽略);菜单动作在独立隐藏进程执行,托盘 UI 永不卡顿。
|
|
59
|
-
|
|
60
|
-
## 三、日常使用
|
|
61
|
-
|
|
62
|
-
1. **自动跟随**:在 dsh 控制台(`~\.dsh\launcher\dsh-control.ps1`)执行 启动/重启 Web 时,托盘会自动拉起(幂等)。
|
|
63
|
-
2. **手动唤起**:双击桌面「DSH 系统托盘」快捷方式;或 `dsh-tray`。
|
|
64
|
-
3. **退出托盘**:右键 → 退出托盘(只退出托盘本身,不影响 dsh;下次登录/双击快捷方式可重新唤起)。
|
|
65
|
-
4. **取消自启**:删除启动文件夹里的「DSH 系统托盘.lnk」。
|
|
66
|
-
|
|
67
|
-
## 四、更新
|
|
68
|
-
|
|
69
|
-
两种方式任选:
|
|
70
|
-
|
|
71
|
-
**方式一:托盘一键更新(推荐)**——托盘菜单「版本信息 ▸」→「检查更新」对比 npm 最新版,发现新版后点「更新到最新版」:自动 `npm install -g` → 重新部署(含本机路径改写)→ 自动重启托盘,失败不影响当前托盘。
|
|
72
|
-
|
|
73
|
-
**方式二:npm 标准流程**:
|
|
74
|
-
|
|
75
|
-
```powershell
|
|
76
|
-
npm up -g dsh-windows-tray # 拉取最新版
|
|
77
|
-
dsh-tray install # 重新部署(自动改写本机路径 + 刷新快捷方式)
|
|
78
|
-
dsh-tray # 重启托盘
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
> 部署目录固定为 `~\.dsh\launcher`,重装/更新会覆盖旧文件,不会残留;`dsh-tray info` 可查看当前版本与部署状态。
|
|
82
|
-
> ⚠️ 更新 = 重新 install,**本机手工适配(端口/数据目录等,见 §六)会被一并覆盖**——更新后按对照表重做并重启托盘,别只看安装输出就收工。
|
|
83
|
-
|
|
84
|
-
## 五、卸载
|
|
85
|
-
|
|
86
|
-
```powershell
|
|
87
|
-
npm rm -g dsh-windows-tray # 移除 npm 包本体
|
|
88
|
-
# 删除两个「DSH 系统托盘.lnk」(启动文件夹 + 桌面)
|
|
89
|
-
# 删除 ~\.dsh\launcher 目录(托盘部署文件;如与 dsh 控制台共用,确认无需保留再删)
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## 六、配置
|
|
93
|
-
|
|
94
|
-
> **先探测实际值再回填,不要照抄默认列**(探测命令见 §一"安装后必做")。同一参数可能有多个填充点,缺改一处就被另一处覆盖。
|
|
95
|
-
|
|
96
|
-
| 项 | 位置 | 包默认 | 说明 |
|
|
97
|
-
| :--- | :--- | :--- | :--- |
|
|
98
|
-
| 监控端口 | ① `~\.dsh\launcher\dsh-lib.ps1` 顶部 `$PORT`;② `dsh-tray.ps1` 顶部 `param([int]$Port = …)` | `3088` | **两处必须一致**:托盘启动时先点源 lib 再执行 `$PORT = $Port`,**param 默认值会把 lib 改好的端口覆盖回去**(dsh-tray.ps1 第 27 行)——只改 lib 不改 param,托盘仍按默认端口探测 |
|
|
99
|
-
| dsh 路径 | `dsh-lib.ps1` 顶部 `$DSH_CMD / $NODE / $BIN` | 自动探测 | install 已改写,一般不用动 |
|
|
100
|
-
| 数据目录 | `dsh-lib.ps1` `$WORKDIR` + `$env:DSH_HOME` | **不设置** | 不设 DSH_HOME 时 web 默认落 `~\.dsh`——自定义数据目录的机器(如 `D:\dsh`)必须在 lib 里补 `$env:DSH_HOME`,否则托盘/控制台启动的 web 指向空目录 |
|
|
101
|
-
| 日志目录 | `dsh-lib.ps1` `$LOG_DIR`(`~\.dsh\logs`) | — | **目录须存在**:不存在时托盘 `Add-Content -ErrorAction SilentlyContinue` 静默吞日志,零日志不是没在记 |
|
|
102
|
-
| 图标颜色 | 包内 `build/build-tray-icons.ps1`(运行蓝/停止灰) | `#4176E6` / `#81858C` | — |
|
|
103
|
-
|
|
104
|
-
> ⚠️ `~\.dsh\launcher` 是部署产物,`dsh-tray install`/更新每次整体覆盖——上表手工适配重装后须重新应用(对照下表逐项核对,别凭记忆)。
|
|
105
|
-
|
|
106
|
-
### 6.1 本机落地对照(跨机器对照用,照此逐项核对)
|
|
107
|
-
|
|
108
|
-
| 项 | 包默认 | 笔记本实际(2026-08-27 实测) | 填充点 |
|
|
109
|
-
| :--- | :--- | :--- | :--- |
|
|
110
|
-
| Web 端口 | 3088 | **3080** | dsh-lib.ps1 `$PORT` + dsh-tray.ps1 param 默认值(两处) |
|
|
111
|
-
| 数据目录 | 不设→`~\.dsh` | **D:\dsh** | dsh-lib.ps1 `$WORKDIR='D:\dsh'` + `$env:DSH_HOME='D:\dsh'` |
|
|
112
|
-
| 日志目录 | `~\.dsh\logs`(可能不存在) | 已手动创建 | — |
|
|
113
|
-
| 旧控制台 | — | `D:\dsh\launcher\dsh-control.ps1` 与托盘部署并存,端口同为 3080 | 无需合并 |
|
|
114
|
-
| 装完验证 | — | `tail ~/.dsh/logs/dsh-tray.log` 应有「托盘启动 (…端口 3080)」;重启 web 应看到「状态: 未运行 → 运行中」翻转 | — |
|
|
115
|
-
|
|
116
|
-
## 七、故障排查
|
|
117
|
-
|
|
118
|
-
| 现象 | 处理 |
|
|
119
|
-
| :--- | :--- |
|
|
120
|
-
| web 明明在跑,托盘灰鲸/悬停显示"未运行" | 端口填充点不一致(十有八九是 `dsh-tray.ps1` 的 param 默认值把 lib 改好的端口覆盖回去了)——`tail ~\.dsh\logs\dsh-tray.log` 看「托盘启动」行的实际端口,对照 §六/§6.1 把两处改齐后重启托盘 |
|
|
121
|
-
| 托盘零日志 | `~\.dsh\logs` 目录不存在,托盘写日志被静默吞掉——建目录后重启托盘;重启 web 应立即出现状态翻转记录 |
|
|
122
|
-
| 任务栏看不到鲸鱼 | 点开 `^` 溢出区查找;若在,拖到任务栏(一次性)。 |
|
|
123
|
-
| 托盘图标消失 / 进程死了 | `dsh-tray` 或双击桌面快捷方式重新唤起;托盘退出不影响 dsh。 |
|
|
124
|
-
| 动作没反应 | 看 `~\.dsh\logs\dsh-tray-actions.log`(菜单动作)与 `dsh-tray.log`(状态变化)。 |
|
|
125
|
-
| 重复实例 | 命名互斥锁单实例保护(被强杀遗留的锁自动接管),正常不会重复。 |
|
|
126
|
-
| 检查更新失败 | 网络或 npm 不可用;托盘继续正常工作,稍后再试。 |
|
|
127
|
-
| dsh 内核升级了,托盘要动吗 | 不用——托盘只探测 3088 端口与 TUI 进程命令行,与内核版本解耦。 |
|
|
128
|
-
|
|
129
|
-
## 八、已知边界
|
|
130
|
-
|
|
131
|
-
- 通知弹窗受 Windows「通知设置/专注助手」控制,被系统屏蔽时图标与菜单不受影响。
|
|
132
|
-
- 托盘进程是 `pwsh`,`taskkill /T` 杀进程树会连托盘一起结束(下次登录/快捷方式/控制台操作时恢复)。
|
|
133
|
-
- 端口探测只区分 运行/停止:3088 被其他程序占用时会误判"运行中"。
|
|
1
|
+
# DSH 系统托盘(Windows)—— npm 包使用手册
|
|
2
|
+
|
|
3
|
+
> 包名: **`dsh-windows-tray`** · 部署位置: `~\.dsh\launcher` · 技术栈: PowerShell 7 + WinForms,零 npm 依赖、纯旁观者、不改 dsh 源码。
|
|
4
|
+
> 安装 / 更新 / 卸载全部走 npm 标准流程;开发归档见 [tray-dev-archive.md](tray-dev-archive.md),相关踩坑见 [powershell-pitfalls.md](powershell-pitfalls.md)。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 一、安装与部署
|
|
9
|
+
|
|
10
|
+
前置:Windows 10/11 + [PowerShell 7](https://github.com/PowerShell/PowerShell/releases)(7.x;缺失时自动回退 Windows PowerShell 5.1)+ Node.js **≥ 18** + 已安装 dsh(任意位置)。
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
# 1. 全局安装
|
|
14
|
+
npm i -g dsh-windows-tray
|
|
15
|
+
|
|
16
|
+
# 2. 一键部署:自动探测 dsh/pwsh/node 路径 → 安装到 ~\.dsh\launcher
|
|
17
|
+
# → 按本机路径改写配置 → 创建开机自启 + 桌面快捷方式 → 启动托盘
|
|
18
|
+
dsh-tray install
|
|
19
|
+
|
|
20
|
+
# 3. 首次使用:把任务栏 ^ 溢出区里的鲸鱼拖到任务栏(一次性,Windows 会记住)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 安装后必做:本机参数探测 → 确认 → 回填(不要信默认值)
|
|
24
|
+
|
|
25
|
+
install 只自动改写 dsh/pwsh/node **路径**;**端口、数据目录按包默认值写入且不改**。与本机实际不一致时,症状是托盘永远灰鲸(探测错端口)或托盘/控制台启动的 web 落到空数据目录。先实测,人工确认数值,再回填到 §六 列出的**全部**填充点(只改一处会被另一处覆盖):
|
|
26
|
+
|
|
27
|
+
```powershell
|
|
28
|
+
# 实际 Web 端口:看运行中的 web 进程命令行(没在跑就看本机控制台/启动脚本里的启动命令)
|
|
29
|
+
Get-CimInstance Win32_Process -Filter "Name='node.exe'" |
|
|
30
|
+
Where-Object { $_.CommandLine -like '*web*' } |
|
|
31
|
+
ForEach-Object { $_.CommandLine } # 输出里找 --port NNNN 与 --host
|
|
32
|
+
|
|
33
|
+
# 实际数据目录:找带 settings.yaml + skins/ + profiles/ 的目录(默认 ~\.dsh,自定义部署不在那)
|
|
34
|
+
Test-Path "$env:USERPROFILE\.dsh\settings.yaml" # True=默认目录;False=自定义 DSH_HOME,必须回填
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
常用命令:
|
|
38
|
+
|
|
39
|
+
| 命令 | 作用 |
|
|
40
|
+
| :--- | :--- |
|
|
41
|
+
| `dsh-tray` | 启动托盘(已有实例则提示,不重复启动) |
|
|
42
|
+
| `dsh-tray install` | 一键部署 / 重新部署(可透传 `-DshRoot` 等参数) |
|
|
43
|
+
| `dsh-tray icons` | 重新生成鲸鱼图标(内置 512px 源图,无需浏览器) |
|
|
44
|
+
| `dsh-tray info` | 查看版本 / 安装位置 / 部署状态 |
|
|
45
|
+
|
|
46
|
+
## 二、功能一览
|
|
47
|
+
|
|
48
|
+
| 交互 | 行为 |
|
|
49
|
+
| :--- | :--- |
|
|
50
|
+
| **图标** | 🐋 品牌蓝鲸鱼 = Web 运行中;灰鲸鱼 = 未运行(官方 favicon 渲染,多尺寸 16~256px) |
|
|
51
|
+
| **悬停提示** | `Web UI 运行中 · 127.0.0.1:3088` / `Web UI 未运行 · 右键可启动` |
|
|
52
|
+
| **双击** | 打开 Web UI(未运行时提示先启动) |
|
|
53
|
+
| **右键菜单** | 顶部 `Web UI 状态` / `TUI 状态` 双状态行(含 PID);`Web UI ▸`(打开 Web UI / 启动 / 停止(带确认) / 重启);`TUI ▸`(启动 / 停止);打开日志目录 / `版本信息 ▸`(版本 / 检查更新 / 更新到最新版)/ 退出托盘 |
|
|
54
|
+
| **菜单智能禁用** | Web 运行中禁用「启动 Web UI」;TUI 运行中禁用「启动 TUI」 |
|
|
55
|
+
| **状态通知** | Web/TUI 启动与停止瞬间弹系统通知;托盘就绪弹一次提示 |
|
|
56
|
+
| **开机自启** | 登录后托盘自动出现(启动文件夹快捷方式,安装时自动创建) |
|
|
57
|
+
|
|
58
|
+
托盘每 2 秒探测一次 `127.0.0.1:3088`(TCP 连接,500ms 超时,开销可忽略);菜单动作在独立隐藏进程执行,托盘 UI 永不卡顿。
|
|
59
|
+
|
|
60
|
+
## 三、日常使用
|
|
61
|
+
|
|
62
|
+
1. **自动跟随**:在 dsh 控制台(`~\.dsh\launcher\dsh-control.ps1`)执行 启动/重启 Web 时,托盘会自动拉起(幂等)。
|
|
63
|
+
2. **手动唤起**:双击桌面「DSH 系统托盘」快捷方式;或 `dsh-tray`。
|
|
64
|
+
3. **退出托盘**:右键 → 退出托盘(只退出托盘本身,不影响 dsh;下次登录/双击快捷方式可重新唤起)。
|
|
65
|
+
4. **取消自启**:删除启动文件夹里的「DSH 系统托盘.lnk」。
|
|
66
|
+
|
|
67
|
+
## 四、更新
|
|
68
|
+
|
|
69
|
+
两种方式任选:
|
|
70
|
+
|
|
71
|
+
**方式一:托盘一键更新(推荐)**——托盘菜单「版本信息 ▸」→「检查更新」对比 npm 最新版,发现新版后点「更新到最新版」:自动 `npm install -g` → 重新部署(含本机路径改写)→ 自动重启托盘,失败不影响当前托盘。
|
|
72
|
+
|
|
73
|
+
**方式二:npm 标准流程**:
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
npm up -g dsh-windows-tray # 拉取最新版
|
|
77
|
+
dsh-tray install # 重新部署(自动改写本机路径 + 刷新快捷方式)
|
|
78
|
+
dsh-tray # 重启托盘
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> 部署目录固定为 `~\.dsh\launcher`,重装/更新会覆盖旧文件,不会残留;`dsh-tray info` 可查看当前版本与部署状态。
|
|
82
|
+
> ⚠️ 更新 = 重新 install,**本机手工适配(端口/数据目录等,见 §六)会被一并覆盖**——更新后按对照表重做并重启托盘,别只看安装输出就收工。
|
|
83
|
+
|
|
84
|
+
## 五、卸载
|
|
85
|
+
|
|
86
|
+
```powershell
|
|
87
|
+
npm rm -g dsh-windows-tray # 移除 npm 包本体
|
|
88
|
+
# 删除两个「DSH 系统托盘.lnk」(启动文件夹 + 桌面)
|
|
89
|
+
# 删除 ~\.dsh\launcher 目录(托盘部署文件;如与 dsh 控制台共用,确认无需保留再删)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 六、配置
|
|
93
|
+
|
|
94
|
+
> **先探测实际值再回填,不要照抄默认列**(探测命令见 §一"安装后必做")。同一参数可能有多个填充点,缺改一处就被另一处覆盖。
|
|
95
|
+
|
|
96
|
+
| 项 | 位置 | 包默认 | 说明 |
|
|
97
|
+
| :--- | :--- | :--- | :--- |
|
|
98
|
+
| 监控端口 | ① `~\.dsh\launcher\dsh-lib.ps1` 顶部 `$PORT`;② `dsh-tray.ps1` 顶部 `param([int]$Port = …)` | `3088` | **两处必须一致**:托盘启动时先点源 lib 再执行 `$PORT = $Port`,**param 默认值会把 lib 改好的端口覆盖回去**(dsh-tray.ps1 第 27 行)——只改 lib 不改 param,托盘仍按默认端口探测 |
|
|
99
|
+
| dsh 路径 | `dsh-lib.ps1` 顶部 `$DSH_CMD / $NODE / $BIN` | 自动探测 | install 已改写,一般不用动 |
|
|
100
|
+
| 数据目录 | `dsh-lib.ps1` `$WORKDIR` + `$env:DSH_HOME` | **不设置** | 不设 DSH_HOME 时 web 默认落 `~\.dsh`——自定义数据目录的机器(如 `D:\dsh`)必须在 lib 里补 `$env:DSH_HOME`,否则托盘/控制台启动的 web 指向空目录 |
|
|
101
|
+
| 日志目录 | `dsh-lib.ps1` `$LOG_DIR`(`~\.dsh\logs`) | — | **目录须存在**:不存在时托盘 `Add-Content -ErrorAction SilentlyContinue` 静默吞日志,零日志不是没在记 |
|
|
102
|
+
| 图标颜色 | 包内 `build/build-tray-icons.ps1`(运行蓝/停止灰) | `#4176E6` / `#81858C` | — |
|
|
103
|
+
|
|
104
|
+
> ⚠️ `~\.dsh\launcher` 是部署产物,`dsh-tray install`/更新每次整体覆盖——上表手工适配重装后须重新应用(对照下表逐项核对,别凭记忆)。
|
|
105
|
+
|
|
106
|
+
### 6.1 本机落地对照(跨机器对照用,照此逐项核对)
|
|
107
|
+
|
|
108
|
+
| 项 | 包默认 | 笔记本实际(2026-08-27 实测) | 填充点 |
|
|
109
|
+
| :--- | :--- | :--- | :--- |
|
|
110
|
+
| Web 端口 | 3088 | **3080** | dsh-lib.ps1 `$PORT` + dsh-tray.ps1 param 默认值(两处) |
|
|
111
|
+
| 数据目录 | 不设→`~\.dsh` | **D:\dsh** | dsh-lib.ps1 `$WORKDIR='D:\dsh'` + `$env:DSH_HOME='D:\dsh'` |
|
|
112
|
+
| 日志目录 | `~\.dsh\logs`(可能不存在) | 已手动创建 | — |
|
|
113
|
+
| 旧控制台 | — | `D:\dsh\launcher\dsh-control.ps1` 与托盘部署并存,端口同为 3080 | 无需合并 |
|
|
114
|
+
| 装完验证 | — | `tail ~/.dsh/logs/dsh-tray.log` 应有「托盘启动 (…端口 3080)」;重启 web 应看到「状态: 未运行 → 运行中」翻转 | — |
|
|
115
|
+
|
|
116
|
+
## 七、故障排查
|
|
117
|
+
|
|
118
|
+
| 现象 | 处理 |
|
|
119
|
+
| :--- | :--- |
|
|
120
|
+
| web 明明在跑,托盘灰鲸/悬停显示"未运行" | 端口填充点不一致(十有八九是 `dsh-tray.ps1` 的 param 默认值把 lib 改好的端口覆盖回去了)——`tail ~\.dsh\logs\dsh-tray.log` 看「托盘启动」行的实际端口,对照 §六/§6.1 把两处改齐后重启托盘 |
|
|
121
|
+
| 托盘零日志 | `~\.dsh\logs` 目录不存在,托盘写日志被静默吞掉——建目录后重启托盘;重启 web 应立即出现状态翻转记录 |
|
|
122
|
+
| 任务栏看不到鲸鱼 | 点开 `^` 溢出区查找;若在,拖到任务栏(一次性)。 |
|
|
123
|
+
| 托盘图标消失 / 进程死了 | `dsh-tray` 或双击桌面快捷方式重新唤起;托盘退出不影响 dsh。 |
|
|
124
|
+
| 动作没反应 | 看 `~\.dsh\logs\dsh-tray-actions.log`(菜单动作)与 `dsh-tray.log`(状态变化)。 |
|
|
125
|
+
| 重复实例 | 命名互斥锁单实例保护(被强杀遗留的锁自动接管),正常不会重复。 |
|
|
126
|
+
| 检查更新失败 | 网络或 npm 不可用;托盘继续正常工作,稍后再试。 |
|
|
127
|
+
| dsh 内核升级了,托盘要动吗 | 不用——托盘只探测 3088 端口与 TUI 进程命令行,与内核版本解耦。 |
|
|
128
|
+
|
|
129
|
+
## 八、已知边界
|
|
130
|
+
|
|
131
|
+
- 通知弹窗受 Windows「通知设置/专注助手」控制,被系统屏蔽时图标与菜单不受影响。
|
|
132
|
+
- 托盘进程是 `pwsh`,`taskkill /T` 杀进程树会连托盘一起结束(下次登录/快捷方式/控制台操作时恢复)。
|
|
133
|
+
- 端口探测只区分 运行/停止:3088 被其他程序占用时会误判"运行中"。
|