dsh-windows-tray 1.0.2 → 1.0.3

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