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/install.ps1
CHANGED
|
@@ -1,184 +1,228 @@
|
|
|
1
|
-
# install.ps1 —— DSH 系统托盘 一键部署(便携包内运行)
|
|
2
|
-
# 用法: pwsh -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 [-DshRoot <目录>] [-PwshPath <路径>] [-NodePath <路径>] [-Port <端口>] [-DshHome <数据目录>] [-Auto] [-NoTrayStart] [-NoShortcuts]
|
|
3
|
-
# 流程: 探测本机路径 -> 探测端口/数据目录并确认 -> 安装到 ~\.dsh\launcher -> 回填全部读取点(含端口两处+DSH_HOME) -> 创建快捷方式 -> 启动托盘
|
|
4
|
-
# v1.0.2: 端口/数据目录实测探测 + 展示确认 + 回填全部读取点 + 自动创建日志目录 (修: param 默认值覆盖 lib 端口 / DSH_HOME 不设 / 日志目录缺失静默丢日志)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[string]$
|
|
8
|
-
[string]$
|
|
9
|
-
[
|
|
10
|
-
[
|
|
11
|
-
[
|
|
12
|
-
[switch]$
|
|
13
|
-
[switch]$
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
$
|
|
17
|
-
$
|
|
18
|
-
$
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
$
|
|
36
|
-
|
|
37
|
-
return $
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
(Join-Path $env:USERPROFILE 'dsh'),
|
|
57
|
-
(Join-Path $env:LOCALAPPDATA 'dsh')
|
|
58
|
-
)
|
|
59
|
-
foreach ($c in $candidates) {
|
|
60
|
-
$c = [System.IO.Path]::GetFullPath($c)
|
|
61
|
-
if (Test-Path (Join-Path $c 'dsh.cmd')) { $DshRoot = $c; break }
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
$dsdCmd = Join-Path $DshRoot 'dsh.cmd'
|
|
65
|
-
if (-not (Test-Path $dsdCmd)) { throw "未找到 dsh 安装目录 (缺少 dsh.cmd: $dsdCmd)。请用 -DshRoot 指定。" }
|
|
66
|
-
Write-Host "[1/7] dsh 根目录: $DshRoot" -ForegroundColor Green
|
|
67
|
-
|
|
68
|
-
# 2) 探测 pwsh (PowerShell 7)
|
|
69
|
-
if (-not $PwshPath) {
|
|
70
|
-
$cmd = Get-Command pwsh -ErrorAction SilentlyContinue
|
|
71
|
-
if ($cmd) { $PwshPath = $cmd.Source }
|
|
72
|
-
elseif (Test-Path '
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
$pwshVer
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
$cmd
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
$nodeVer
|
|
90
|
-
if ($
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
$probed =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
$probed =
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
$
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
$
|
|
116
|
-
$
|
|
117
|
-
$bin
|
|
118
|
-
|
|
119
|
-
$
|
|
120
|
-
'$
|
|
121
|
-
'$
|
|
122
|
-
'$
|
|
123
|
-
'$
|
|
124
|
-
'$
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
$
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
$
|
|
135
|
-
$
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
$trayText =
|
|
146
|
-
$
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
$
|
|
155
|
-
$
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
@{ Dir = [Environment]::GetFolderPath('
|
|
159
|
-
@{ Dir = [Environment]::GetFolderPath('
|
|
160
|
-
)) {
|
|
161
|
-
$lnk = $ws.CreateShortcut((Join-Path $target.Dir $target.Name))
|
|
162
|
-
$lnk.TargetPath = $PwshPath
|
|
163
|
-
$lnk.Arguments = $argsLine
|
|
164
|
-
$lnk.WorkingDirectory = $dst
|
|
165
|
-
$lnk.IconLocation = $icon
|
|
166
|
-
$lnk.WindowStyle = 7
|
|
167
|
-
$lnk.Description = $target.Desc
|
|
168
|
-
$lnk.Save()
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
1
|
+
# install.ps1 —— DSH 系统托盘 一键部署(便携包内运行)
|
|
2
|
+
# 用法: pwsh -NoProfile -ExecutionPolicy Bypass -File .\install.ps1 [-DshRoot <目录>] [-PwshPath <路径>] [-NodePath <路径>] [-Port <端口>] [-DshHome <数据目录>] [-Auto] [-NoTrayStart] [-NoShortcuts]
|
|
3
|
+
# 流程: 探测本机路径 -> 探测端口/数据目录并确认 -> 安装到 ~\.dsh\launcher -> 回填全部读取点(含端口两处+DSH_HOME) -> 创建快捷方式 -> 启动托盘
|
|
4
|
+
# v1.0.2: 端口/数据目录实测探测 + 展示确认 + 回填全部读取点 + 自动创建日志目录 (修: param 默认值覆盖 lib 端口 / DSH_HOME 不设 / 日志目录缺失静默丢日志)
|
|
5
|
+
# v1.0.3: 停止确认框枚举修复(DefaultButton2→Button2, Win11 .NET 崩溃框根因) + AUMID 通知身份(开始菜单快捷方式+预授权, 气泡不再显示为 PowerShell)
|
|
6
|
+
param(
|
|
7
|
+
[string]$DshRoot, # dsh 安装根目录 (含 dsh.cmd); 缺省自动探测
|
|
8
|
+
[string]$PwshPath, # pwsh.exe 路径; 缺省自动探测
|
|
9
|
+
[string]$NodePath, # node.exe 路径; 缺省自动探测
|
|
10
|
+
[int]$Port = 0, # Web 端口; 0=从运行中的 web 进程命令行探测
|
|
11
|
+
[string]$DshHome, # 数据目录 (含 settings.yaml); 空=自动探测
|
|
12
|
+
[switch]$Auto, # 非交互: 直接采用探测值不确认 (自更新等隐藏窗口场景)
|
|
13
|
+
[switch]$NoTrayStart, # 部署后不自动启动托盘
|
|
14
|
+
[switch]$NoShortcuts # 不创建快捷方式
|
|
15
|
+
)
|
|
16
|
+
$ErrorActionPreference = 'Stop'
|
|
17
|
+
$pkgRoot = $PSScriptRoot
|
|
18
|
+
$dst = Join-Path $env:USERPROFILE '.dsh\launcher'
|
|
19
|
+
$logDir = Join-Path $env:USERPROFILE '.dsh\logs'
|
|
20
|
+
|
|
21
|
+
Write-Host '== DSH 系统托盘 一键部署 ==' -ForegroundColor Cyan
|
|
22
|
+
|
|
23
|
+
# ---- 探测: 实际 Web 端口 (从运行中的 web 进程命令行提取, 不写死默认值) ----
|
|
24
|
+
function Find-WebPort {
|
|
25
|
+
$procs = @(Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
|
|
26
|
+
Where-Object { $_.CommandLine -match 'web' -and $_.CommandLine -match '--port' })
|
|
27
|
+
foreach ($p in $procs) {
|
|
28
|
+
if ($p.CommandLine -match '--port\s+(\d+)') { return [int]$Matches[1] }
|
|
29
|
+
}
|
|
30
|
+
return $null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# ---- 探测: 实际数据目录 (特征 = settings.yaml; 环境变量优先, 其次默认 ~\.dsh) ----
|
|
34
|
+
function Find-DshHome {
|
|
35
|
+
if ($env:DSH_HOME -and (Test-Path (Join-Path $env:DSH_HOME 'settings.yaml'))) { return $env:DSH_HOME }
|
|
36
|
+
$def = Join-Path $env:USERPROFILE '.dsh'
|
|
37
|
+
if (Test-Path (Join-Path $def 'settings.yaml')) { return $def }
|
|
38
|
+
return $null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# ---- 交互确认 (Auto 或 stdin 不可用时直接采用探测值) ----
|
|
42
|
+
function Confirm-Value([string]$label, [string]$current) {
|
|
43
|
+
if ($Auto) { return $current }
|
|
44
|
+
try {
|
|
45
|
+
$answer = Read-Host "$label [$current]"
|
|
46
|
+
if (-not [string]::IsNullOrWhiteSpace($answer)) { return $answer.Trim() }
|
|
47
|
+
} catch { } # 非交互环境: Read-Host 失败 -> 采用探测值
|
|
48
|
+
return $current
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# 1) 探测 dsh 安装根目录
|
|
52
|
+
if (-not $DshRoot) {
|
|
53
|
+
$candidates = @(
|
|
54
|
+
(Join-Path $pkgRoot '..\..'), # 包直接放在 dsh 安装根内: <根>\dist\dsh-tray-package\
|
|
55
|
+
(Join-Path $env:ProgramFiles 'dsh'),
|
|
56
|
+
(Join-Path $env:USERPROFILE 'dsh'),
|
|
57
|
+
(Join-Path $env:LOCALAPPDATA 'dsh')
|
|
58
|
+
)
|
|
59
|
+
foreach ($c in $candidates) {
|
|
60
|
+
$c = [System.IO.Path]::GetFullPath($c)
|
|
61
|
+
if (Test-Path (Join-Path $c 'dsh.cmd')) { $DshRoot = $c; break }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
$dsdCmd = Join-Path $DshRoot 'dsh.cmd'
|
|
65
|
+
if (-not (Test-Path $dsdCmd)) { throw "未找到 dsh 安装目录 (缺少 dsh.cmd: $dsdCmd)。请用 -DshRoot 指定。" }
|
|
66
|
+
Write-Host "[1/7] dsh 根目录: $DshRoot" -ForegroundColor Green
|
|
67
|
+
|
|
68
|
+
# 2) 探测 pwsh (PowerShell 7)
|
|
69
|
+
if (-not $PwshPath) {
|
|
70
|
+
$cmd = Get-Command pwsh -ErrorAction SilentlyContinue
|
|
71
|
+
if ($cmd) { $PwshPath = $cmd.Source }
|
|
72
|
+
elseif (Test-Path (Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe')) { $PwshPath = Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe' }
|
|
73
|
+
}
|
|
74
|
+
if (-not $PwshPath -or -not (Test-Path $PwshPath)) { throw "未找到 pwsh (PowerShell 7)。请用 -PwshPath 指定。" }
|
|
75
|
+
# 版本校验: 托盘常驻进程依赖 PowerShell 7.x (5.1 只能降级运行)
|
|
76
|
+
$pwshVer = & $PwshPath -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>$null
|
|
77
|
+
if ($pwshVer -notmatch '^7\.') { throw "pwsh 必须是 PowerShell 7.x (检测到: $pwshVer)。请安装 PowerShell 7 或用 -PwshPath 指定。" }
|
|
78
|
+
Write-Host "[2/7] pwsh: $PwshPath (v$pwshVer)" -ForegroundColor Green
|
|
79
|
+
|
|
80
|
+
# 3) 探测 node
|
|
81
|
+
if (-not $NodePath) {
|
|
82
|
+
$cmd = Get-Command node -ErrorAction SilentlyContinue
|
|
83
|
+
if ($cmd) { $NodePath = $cmd.Source }
|
|
84
|
+
elseif (Test-Path 'C:\Program Files\nodejs\node.exe') { $NodePath = 'C:\Program Files\nodejs\node.exe' }
|
|
85
|
+
}
|
|
86
|
+
if (-not $NodePath -or -not (Test-Path $NodePath)) { throw "未找到 node.exe。请用 -NodePath 指定。" }
|
|
87
|
+
# 版本校验: 需要 ≥ 18 (dsh 本体运行要求)
|
|
88
|
+
$nodeVer = & $NodePath -v 2>$null
|
|
89
|
+
if ($nodeVer -notmatch '^v(\d+)\.') { throw "无法识别 node 版本 (检测到: $nodeVer)。请用 -NodePath 指定有效 node.exe。" }
|
|
90
|
+
if ([int]$Matches[1] -lt 18) { throw "node 必须 ≥ 18 (检测到: $nodeVer)。请升级 Node.js 或用 -NodePath 指定。" }
|
|
91
|
+
Write-Host "[3/7] node: $NodePath (v$($Matches[1]))" -ForegroundColor Green
|
|
92
|
+
|
|
93
|
+
# 4) 探测 Web 端口 + 数据目录, 展示确认
|
|
94
|
+
if ($Port -eq 0) {
|
|
95
|
+
$probed = Find-WebPort
|
|
96
|
+
if ($probed) { $Port = $probed }
|
|
97
|
+
}
|
|
98
|
+
if (-not $DshHome) {
|
|
99
|
+
$probed = Find-DshHome
|
|
100
|
+
if ($probed) { $DshHome = $probed }
|
|
101
|
+
}
|
|
102
|
+
if ($Port -eq 0) { $Port = 3088; Write-Host '警告: 未检测到运行中的 web 进程, 端口取包默认 3088 (如不符请用 -Port 指定)' -ForegroundColor Yellow }
|
|
103
|
+
if (-not $DshHome) { $DshHome = Join-Path $env:USERPROFILE '.dsh'; Write-Host "警告: 未探测到数据目录 (无 settings.yaml), 取默认 $DshHome (如不符请用 -DshHome 指定)" -ForegroundColor Yellow }
|
|
104
|
+
$Port = [int](Confirm-Value 'Web 端口' "$Port")
|
|
105
|
+
$DshHome = Confirm-Value '数据目录' $DshHome
|
|
106
|
+
Write-Host "[4/7] Web 端口: $Port ; 数据目录: $DshHome" -ForegroundColor Green
|
|
107
|
+
|
|
108
|
+
# 5) 安装到 ~\.dsh\launcher
|
|
109
|
+
New-Item -ItemType Directory -Force -Path $dst | Out-Null
|
|
110
|
+
Copy-Item -Path (Join-Path $pkgRoot 'launcher\*') -Destination $dst -Recurse -Force
|
|
111
|
+
Write-Host ('[5/7] 已安装到 ' + $dst) -ForegroundColor Green
|
|
112
|
+
|
|
113
|
+
# 6) 回填全部读取点 + 自动创建日志目录
|
|
114
|
+
$lib = Join-Path $dst 'dsh-lib.ps1'
|
|
115
|
+
$tray = Join-Path $dst 'dsh-tray.ps1'
|
|
116
|
+
$bin = Join-Path $DshRoot 'node_modules\@deepseek-ai\dsh\lib\bin.js'
|
|
117
|
+
if (-not (Test-Path $bin)) { Write-Host "警告: 未找到 $bin (dsh 包结构可能不同), 请检查 dsh-lib.ps1 的 \$BIN" -ForegroundColor Yellow }
|
|
118
|
+
$map = [ordered]@{
|
|
119
|
+
'$DSH_CMD = ' = "`$DSH_CMD = '$($DshRoot -replace "'", "''")\dsh.cmd'"
|
|
120
|
+
'$NODE = ' = "`$NODE = '$($NodePath -replace "'", "''")'"
|
|
121
|
+
'$BIN = ' = "`$BIN = '$($bin -replace "'", "''")'"
|
|
122
|
+
'$WORKDIR = ' = "`$WORKDIR = '$($DshRoot -replace "'", "''")'"
|
|
123
|
+
'$PORT = ' = "`$PORT = $Port"
|
|
124
|
+
'$PWSH = ' = "`$PWSH = '$($PwshPath -replace "'", "''")'"
|
|
125
|
+
}
|
|
126
|
+
$lines = Get-Content $lib
|
|
127
|
+
for ($i = 0; $i -lt $lines.Count; $i++) {
|
|
128
|
+
foreach ($k in $map.Keys) {
|
|
129
|
+
if ($lines[$i].StartsWith($k)) { $lines[$i] = $map[$k]; break }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
# DSH_HOME: 先清旧行, 非默认数据目录时写入 (默认 ~\.dsh 不设 = dsh 原生行为)
|
|
133
|
+
$lines = @($lines | Where-Object { $_ -notmatch '^\$env:DSH_HOME\s*=' })
|
|
134
|
+
$defaultHome = Join-Path $env:USERPROFILE '.dsh'
|
|
135
|
+
if ($DshHome -ne $defaultHome) {
|
|
136
|
+
$lines = @($lines | ForEach-Object {
|
|
137
|
+
if ($_ -match '^\$WORKDIR') { $_; "`$env:DSH_HOME = '$($DshHome -replace "'", "''")' # 数据目录 (install 回填)" }
|
|
138
|
+
else { $_ }
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
Set-Content -Path $lib -Value $lines -Encoding utf8
|
|
142
|
+
# dsh-tray.ps1 param 默认值 (点源 lib 后 $PORT = $Port 会把 lib 改好的端口覆盖回去, 必须两处一致)
|
|
143
|
+
# 注意: param 块是多行格式 (param( 换行 [int]$Port = …), 只匹配参数行本身
|
|
144
|
+
$trayText = Get-Content $tray -Raw
|
|
145
|
+
$trayText = $trayText -replace '\[int\]\$Port = \d+', "[int]`$Port = $Port"
|
|
146
|
+
Set-Content -Path $tray -Value $trayText -Encoding utf8
|
|
147
|
+
# 自动创建日志目录 (缺失时 Add-Content -ErrorAction SilentlyContinue 会静默丢日志, 零排查线索)
|
|
148
|
+
New-Item -ItemType Directory -Force -Path $logDir | Out-Null
|
|
149
|
+
Write-Host "[6/7] 已回填 (端口 $Port ×2处 + DSH_HOME) + 日志目录就绪" -ForegroundColor Green
|
|
150
|
+
|
|
151
|
+
# 快捷方式: 开机自启 + 桌面 + 开始菜单(通知身份)
|
|
152
|
+
if (-not $NoShortcuts) {
|
|
153
|
+
$ws = New-Object -ComObject WScript.Shell
|
|
154
|
+
$icon = Join-Path $dst 'icons\dsh-tray-running.ico,0'
|
|
155
|
+
$argsLine = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + (Join-Path $dst 'dsh-tray.ps1') + '"'
|
|
156
|
+
foreach ($target in @(
|
|
157
|
+
@{ Dir = [Environment]::GetFolderPath('Startup'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 鲸鱼图标=运行状态, 右键控制' },
|
|
158
|
+
@{ Dir = [Environment]::GetFolderPath('Desktop'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘 - 双击唤起' },
|
|
159
|
+
@{ Dir = [Environment]::GetFolderPath('Programs'); Name = 'DSH 系统托盘.lnk'; Desc = 'DSH 系统托盘' }
|
|
160
|
+
)) {
|
|
161
|
+
$lnk = $ws.CreateShortcut((Join-Path $target.Dir $target.Name))
|
|
162
|
+
$lnk.TargetPath = $PwshPath
|
|
163
|
+
$lnk.Arguments = $argsLine
|
|
164
|
+
$lnk.WorkingDirectory = $dst
|
|
165
|
+
$lnk.IconLocation = $icon
|
|
166
|
+
$lnk.WindowStyle = 7
|
|
167
|
+
$lnk.Description = $target.Desc
|
|
168
|
+
$lnk.Save()
|
|
169
|
+
# v1.0.3: 给 .lnk 写 System.AppUserModel.ID —— 气泡通知归属「DSH 系统托盘」而非 PowerShell
|
|
170
|
+
if (-not ('DSH.Shortcut.Helper' -as [type])) {
|
|
171
|
+
Add-Type -TypeDefinition @'
|
|
172
|
+
using System;
|
|
173
|
+
using System.Runtime.InteropServices;
|
|
174
|
+
namespace DSH.Shortcut {
|
|
175
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
176
|
+
public struct PropertyKey {
|
|
177
|
+
public Guid fmtid; public uint pid;
|
|
178
|
+
public PropertyKey(Guid g, uint p) { fmtid = g; pid = p; }
|
|
179
|
+
}
|
|
180
|
+
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
|
181
|
+
public struct PropVariant {
|
|
182
|
+
public short vt; public short r1; public short r2; public short r3;
|
|
183
|
+
public IntPtr ptr; public int x1; public int x2;
|
|
184
|
+
}
|
|
185
|
+
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
186
|
+
public interface IPropertyStore {
|
|
187
|
+
void GetCount(out uint c); void GetAt(uint i, out PropertyKey k);
|
|
188
|
+
void GetValue(ref PropertyKey k, out PropVariant v);
|
|
189
|
+
void SetValue(ref PropertyKey k, ref PropVariant v); void Commit();
|
|
190
|
+
}
|
|
191
|
+
public static class Helper {
|
|
192
|
+
[DllImport("shell32.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
|
|
193
|
+
private static extern void SHGetPropertyStoreFromParsingName(string path, IntPtr zero, uint flags, ref Guid iid, [MarshalAs(UnmanagedType.Interface)] out IPropertyStore store);
|
|
194
|
+
public static void SetAumid(string lnkPath, string aumid) {
|
|
195
|
+
var key = new PropertyKey(new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"), 5); // System.AppUserModel.ID
|
|
196
|
+
var pv = new PropVariant(); pv.vt = 31; pv.ptr = Marshal.StringToCoTaskMemUni(aumid); // VT_LPWSTR
|
|
197
|
+
try {
|
|
198
|
+
Guid iid = typeof(IPropertyStore).GUID; IPropertyStore store;
|
|
199
|
+
SHGetPropertyStoreFromParsingName(lnkPath, IntPtr.Zero, 2, ref iid, out store); // GPS_READWRITE
|
|
200
|
+
store.SetValue(ref key, ref pv); store.Commit();
|
|
201
|
+
} finally { Marshal.FreeCoTaskMem(pv.ptr); }
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
'@
|
|
206
|
+
}
|
|
207
|
+
[DSH.Shortcut.Helper]::SetAumid((Join-Path $target.Dir $target.Name), 'DSH.WindowsTray')
|
|
208
|
+
}
|
|
209
|
+
# v1.0.3: 预授权通知(Win11 升级会清应用通知授权, 提前放行 DSH.WindowsTray)
|
|
210
|
+
$nsKey = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Notifications\Settings\DSH.WindowsTray'
|
|
211
|
+
New-Item -Path $nsKey -Force | Out-Null
|
|
212
|
+
Set-ItemProperty -Path $nsKey -Name 'Enabled' -Value 1 -Type DWord
|
|
213
|
+
Set-ItemProperty -Path $nsKey -Name 'ShowInActionCenter' -Value 1 -Type DWord
|
|
214
|
+
Write-Host '快捷方式: 开机自启 + 桌面 + 开始菜单 已创建 (AUMID=DSH.WindowsTray, 通知已预授权)' -ForegroundColor Green
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# 启动托盘
|
|
218
|
+
if (-not $NoTrayStart) {
|
|
219
|
+
Start-Process -FilePath $PwshPath -ArgumentList @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', (Join-Path $dst 'dsh-tray.ps1')) -WindowStyle Hidden
|
|
220
|
+
Write-Host '托盘已启动 (若图标在 ^ 溢出区, 拖到任务栏即可)' -ForegroundColor Green
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
Write-Host ''
|
|
224
|
+
Write-Host '部署完成! 常用命令:' -ForegroundColor Cyan
|
|
225
|
+
Write-Host " 手动唤起托盘: 桌面「DSH 系统托盘」/ 控制台选项 5"
|
|
226
|
+
Write-Host ' 取消自启: 删除启动文件夹里的「DSH 系统托盘.lnk」'
|
|
227
|
+
Write-Host ' 卸载: 删除快捷方式 + 删除 ~\.dsh\launcher'
|
|
228
|
+
Write-Host " 验证: $logDir\dsh-tray.log 首行「托盘启动 (…端口 $Port)」" -ForegroundColor Cyan
|
package/launcher/dsh-actions.ps1
CHANGED
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
# dsh-actions.ps1 —— 托盘菜单动作执行器 (由 dsh-tray.ps1 以独立隐藏进程拉起)
|
|
2
|
-
# 用法: pwsh -NoProfile -File dsh-actions.ps1 -Action start|stop|restart|tui-start|tui-stop
|
|
3
|
-
# 动作在独立进程执行, 托盘 UI 线程保持响应; 日志写入 ~/.dsh/logs/dsh-tray-actions.log
|
|
4
|
-
param(
|
|
5
|
-
[Parameter(Mandatory = $true)]
|
|
6
|
-
[ValidateSet('start', 'stop', 'restart', 'tui-start', 'tui-stop')]
|
|
7
|
-
[string]$Action
|
|
8
|
-
)
|
|
9
|
-
$ErrorActionPreference = 'Stop'
|
|
10
|
-
|
|
11
|
-
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
12
|
-
|
|
13
|
-
$logFile = Join-Path $LOG_DIR 'dsh-tray-actions.log'
|
|
14
|
-
function Log([string]$msg) {
|
|
15
|
-
$line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg)
|
|
16
|
-
Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
switch ($Action) {
|
|
21
|
-
'start' {
|
|
22
|
-
Log '启动 DSH (来自系统托盘)'
|
|
23
|
-
Start-Dsh -OpenBrowser:$false
|
|
24
|
-
Log '启动动作完成'
|
|
25
|
-
}
|
|
26
|
-
'stop' {
|
|
27
|
-
Log '停止 DSH (来自系统托盘)'
|
|
28
|
-
Stop-Dsh
|
|
29
|
-
Log '停止动作完成'
|
|
30
|
-
}
|
|
31
|
-
'restart' {
|
|
32
|
-
Log '重启 DSH (来自系统托盘)'
|
|
33
|
-
Restart-Dsh
|
|
34
|
-
Log '重启动作完成'
|
|
35
|
-
}
|
|
36
|
-
'tui-start' {
|
|
37
|
-
Log '启动 TUI (来自系统托盘)'
|
|
38
|
-
Start-Tui
|
|
39
|
-
Log 'TUI 启动动作完成'
|
|
40
|
-
}
|
|
41
|
-
'tui-stop' {
|
|
42
|
-
Log '停止 TUI (来自系统托盘)'
|
|
43
|
-
Stop-Tui
|
|
44
|
-
Log 'TUI 停止动作完成'
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
} catch {
|
|
48
|
-
Log ("动作 {0} 失败: {1}" -f $Action, $_.Exception.Message)
|
|
49
|
-
exit 1
|
|
50
|
-
}
|
|
1
|
+
# dsh-actions.ps1 —— 托盘菜单动作执行器 (由 dsh-tray.ps1 以独立隐藏进程拉起)
|
|
2
|
+
# 用法: pwsh -NoProfile -File dsh-actions.ps1 -Action start|stop|restart|tui-start|tui-stop
|
|
3
|
+
# 动作在独立进程执行, 托盘 UI 线程保持响应; 日志写入 ~/.dsh/logs/dsh-tray-actions.log
|
|
4
|
+
param(
|
|
5
|
+
[Parameter(Mandatory = $true)]
|
|
6
|
+
[ValidateSet('start', 'stop', 'restart', 'tui-start', 'tui-stop')]
|
|
7
|
+
[string]$Action
|
|
8
|
+
)
|
|
9
|
+
$ErrorActionPreference = 'Stop'
|
|
10
|
+
|
|
11
|
+
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
12
|
+
|
|
13
|
+
$logFile = Join-Path $LOG_DIR 'dsh-tray-actions.log'
|
|
14
|
+
function Log([string]$msg) {
|
|
15
|
+
$line = ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $msg)
|
|
16
|
+
Add-Content -Path $logFile -Value $line -ErrorAction SilentlyContinue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
switch ($Action) {
|
|
21
|
+
'start' {
|
|
22
|
+
Log '启动 DSH (来自系统托盘)'
|
|
23
|
+
Start-Dsh -OpenBrowser:$false
|
|
24
|
+
Log '启动动作完成'
|
|
25
|
+
}
|
|
26
|
+
'stop' {
|
|
27
|
+
Log '停止 DSH (来自系统托盘)'
|
|
28
|
+
Stop-Dsh
|
|
29
|
+
Log '停止动作完成'
|
|
30
|
+
}
|
|
31
|
+
'restart' {
|
|
32
|
+
Log '重启 DSH (来自系统托盘)'
|
|
33
|
+
Restart-Dsh
|
|
34
|
+
Log '重启动作完成'
|
|
35
|
+
}
|
|
36
|
+
'tui-start' {
|
|
37
|
+
Log '启动 TUI (来自系统托盘)'
|
|
38
|
+
Start-Tui
|
|
39
|
+
Log 'TUI 启动动作完成'
|
|
40
|
+
}
|
|
41
|
+
'tui-stop' {
|
|
42
|
+
Log '停止 TUI (来自系统托盘)'
|
|
43
|
+
Stop-Tui
|
|
44
|
+
Log 'TUI 停止动作完成'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
Log ("动作 {0} 失败: {1}" -f $Action, $_.Exception.Message)
|
|
49
|
+
exit 1
|
|
50
|
+
}
|
package/launcher/dsh-control.ps1
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
# DSH 控制台 —— 启动/重启 Web + 启动 TUI + 状态查看 + 系统托盘
|
|
2
|
-
# 部署:2026-08-26;双击桌面「DSH 控制台」或 dsh-control.bat
|
|
3
|
-
# 坑:本脚本由用户独立窗口运行(非 web 子进程),杀/重启 web 安全(PITFALLS §12.2)
|
|
4
|
-
# 2026-08-28: 启动/停止/重启/托盘逻辑抽到 dsh-lib.ps1(与系统托盘共享同一份代码)
|
|
5
|
-
$ErrorActionPreference = 'Stop'
|
|
6
|
-
chcp 65001 > $null
|
|
7
|
-
|
|
8
|
-
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
9
|
-
|
|
10
|
-
while ($true) {
|
|
11
|
-
Clear-Host
|
|
12
|
-
Write-Host ''
|
|
13
|
-
Write-Host ' ┌──────────────────────────────┐'
|
|
14
|
-
Write-Host ' │ DSH 控制台 │'
|
|
15
|
-
Write-Host ' ├──────────────────────────────┤'
|
|
16
|
-
Write-Host ' │ 1) 启动 / 打开 Web (3088) │'
|
|
17
|
-
Write-Host ' │ 2) 重启 Web │'
|
|
18
|
-
Write-Host ' │ 3) 启动 TUI(新终端窗口) │'
|
|
19
|
-
Write-Host ' │ 4) 查看状态 │'
|
|
20
|
-
Write-Host ' │ 5) 启动系统托盘 │'
|
|
21
|
-
Write-Host ' │ 0) 退出 │'
|
|
22
|
-
Write-Host ' └──────────────────────────────┘'
|
|
23
|
-
$c = Read-Host ' 请选择'
|
|
24
|
-
switch ($c) {
|
|
25
|
-
'1' { Start-Dsh }
|
|
26
|
-
'2' { Restart-Dsh }
|
|
27
|
-
'3' { Start-Tui }
|
|
28
|
-
'4' { Show-Status }
|
|
29
|
-
'5' {
|
|
30
|
-
if (Ensure-DshTray) {
|
|
31
|
-
Write-Host '[tray] 系统托盘已在运行(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
32
|
-
} else {
|
|
33
|
-
Write-Host '[tray] 系统托盘已启动(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
'0' { Write-Host ' 再见!'; break }
|
|
37
|
-
default { Write-Host " 无效选择: $c" -ForegroundColor Red }
|
|
38
|
-
}
|
|
39
|
-
if ($c -ne '0') { Write-Host ''; Read-Host ' 按回车返回菜单' }
|
|
40
|
-
}
|
|
1
|
+
# DSH 控制台 —— 启动/重启 Web + 启动 TUI + 状态查看 + 系统托盘
|
|
2
|
+
# 部署:2026-08-26;双击桌面「DSH 控制台」或 dsh-control.bat
|
|
3
|
+
# 坑:本脚本由用户独立窗口运行(非 web 子进程),杀/重启 web 安全(PITFALLS §12.2)
|
|
4
|
+
# 2026-08-28: 启动/停止/重启/托盘逻辑抽到 dsh-lib.ps1(与系统托盘共享同一份代码)
|
|
5
|
+
$ErrorActionPreference = 'Stop'
|
|
6
|
+
chcp 65001 > $null
|
|
7
|
+
|
|
8
|
+
. (Join-Path $PSScriptRoot 'dsh-lib.ps1')
|
|
9
|
+
|
|
10
|
+
while ($true) {
|
|
11
|
+
Clear-Host
|
|
12
|
+
Write-Host ''
|
|
13
|
+
Write-Host ' ┌──────────────────────────────┐'
|
|
14
|
+
Write-Host ' │ DSH 控制台 │'
|
|
15
|
+
Write-Host ' ├──────────────────────────────┤'
|
|
16
|
+
Write-Host ' │ 1) 启动 / 打开 Web (3088) │'
|
|
17
|
+
Write-Host ' │ 2) 重启 Web │'
|
|
18
|
+
Write-Host ' │ 3) 启动 TUI(新终端窗口) │'
|
|
19
|
+
Write-Host ' │ 4) 查看状态 │'
|
|
20
|
+
Write-Host ' │ 5) 启动系统托盘 │'
|
|
21
|
+
Write-Host ' │ 0) 退出 │'
|
|
22
|
+
Write-Host ' └──────────────────────────────┘'
|
|
23
|
+
$c = Read-Host ' 请选择'
|
|
24
|
+
switch ($c) {
|
|
25
|
+
'1' { Start-Dsh }
|
|
26
|
+
'2' { Restart-Dsh }
|
|
27
|
+
'3' { Start-Tui }
|
|
28
|
+
'4' { Show-Status }
|
|
29
|
+
'5' {
|
|
30
|
+
if (Ensure-DshTray) {
|
|
31
|
+
Write-Host '[tray] 系统托盘已在运行(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
32
|
+
} else {
|
|
33
|
+
Write-Host '[tray] 系统托盘已启动(蓝鲸=运行中 / 灰鲸=未运行,右键可控制)' -ForegroundColor Green
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
'0' { Write-Host ' 再见!'; break }
|
|
37
|
+
default { Write-Host " 无效选择: $c" -ForegroundColor Red }
|
|
38
|
+
}
|
|
39
|
+
if ($c -ne '0') { Write-Host ''; Read-Host ' 按回车返回菜单' }
|
|
40
|
+
}
|