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/LICENSE +21 -21
- package/README.md +146 -146
- package/bin/dsh-tray.js +100 -100
- package/build/build-tray-icons.ps1 +98 -98
- package/build/whale-render.html +8 -8
- package/docs/tray-dev-archive.md +57 -57
- package/install.ps1 +230 -184
- package/launcher/bh-menu.ps1 +56 -56
- package/launcher/dsh-actions.ps1 +50 -50
- package/launcher/dsh-all-menu.ps1 +106 -106
- package/launcher/dsh-control.ps1 +40 -40
- package/launcher/dsh-lib.ps1 +1 -1
- 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 +36 -36
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
|
-
|
|
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
|
-
|
|
57
|
-
(Join-Path $env:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
elseif (Test-Path '
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (-
|
|
105
|
-
$
|
|
106
|
-
$
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
$
|
|
117
|
-
$
|
|
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
|
-
$
|
|
146
|
-
$trayText = $
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
$
|
|
156
|
-
$
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@{ Dir = [Environment]::GetFolderPath('
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
$lnk
|
|
164
|
-
$lnk.
|
|
165
|
-
$lnk.
|
|
166
|
-
$lnk.
|
|
167
|
-
$lnk.
|
|
168
|
-
$lnk.
|
|
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
|
+
'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
|
package/launcher/bh-menu.ps1
CHANGED
|
@@ -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
|
+
}
|