panrouter 1.9.0 → 2.0.0
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/cli.mjs +52 -85
- package/package.json +1 -1
- package/tray-daemon.ps1 +71 -51
package/cli.mjs
CHANGED
|
@@ -2,14 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Pan Router CLI — 一键安装 + 启动
|
|
5
|
-
*
|
|
6
|
-
* 用法:
|
|
7
|
-
* panrouter # 一键安装 + 前台启动
|
|
8
|
-
* panrouter --install # 只安装配置
|
|
9
|
-
* panrouter --server # 只启动代理(前台)
|
|
10
|
-
* panrouter --tray # 以托盘模式启动(隐藏窗口)
|
|
11
|
-
* panrouter --tray-install # 安装 + 配置 + 托盘启动
|
|
12
|
-
* panrouter --help # 帮助
|
|
13
5
|
*/
|
|
14
6
|
|
|
15
7
|
import { execSync, spawn } from "node:child_process";
|
|
@@ -48,7 +40,7 @@ function runSilent(cmd) {
|
|
|
48
40
|
}
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
// ─── 1.
|
|
43
|
+
// ─── 1. 安装与配置 ──────────────────────────────────────────
|
|
52
44
|
|
|
53
45
|
function installClaudeCode() {
|
|
54
46
|
log("..", "正在检查 Claude Code...", "yellow");
|
|
@@ -56,7 +48,6 @@ function installClaudeCode() {
|
|
|
56
48
|
log("OK", "Claude Code 已就绪", "green");
|
|
57
49
|
return true;
|
|
58
50
|
}
|
|
59
|
-
|
|
60
51
|
log("..", "正在安装 Claude Code...", "yellow");
|
|
61
52
|
if (!run("npm install -g @anthropic-ai/claude-code")) {
|
|
62
53
|
log("!!", "Claude Code 安装失败", "red");
|
|
@@ -66,20 +57,10 @@ function installClaudeCode() {
|
|
|
66
57
|
return true;
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
// ─── 2. 写入配置文件 ─────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
60
|
function writeConfig() {
|
|
72
61
|
log("..", "正在写入配置...", "yellow");
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 备份
|
|
79
|
-
if (fs.existsSync(SETTINGS_PATH)) {
|
|
80
|
-
fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
|
|
81
|
-
log("OK", "原配置已备份", "green");
|
|
82
|
-
}
|
|
62
|
+
if (!fs.existsSync(CLAUDE_DIR)) fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
63
|
+
if (fs.existsSync(SETTINGS_PATH)) fs.copyFileSync(SETTINGS_PATH, BACKUP_PATH);
|
|
83
64
|
|
|
84
65
|
const config = {
|
|
85
66
|
env: {
|
|
@@ -91,13 +72,10 @@ function writeConfig() {
|
|
|
91
72
|
},
|
|
92
73
|
hasCompletedOnboarding: true,
|
|
93
74
|
};
|
|
94
|
-
|
|
95
75
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
96
|
-
log("OK", "配置文件已写入
|
|
76
|
+
log("OK", "配置文件已写入", "green");
|
|
97
77
|
}
|
|
98
78
|
|
|
99
|
-
// ─── 3. 启动代理服务器(后台运行) ───────────────────────────────────────
|
|
100
|
-
|
|
101
79
|
async function isPortOpen() {
|
|
102
80
|
return new Promise(rs => {
|
|
103
81
|
const req = http.get("http://127.0.0.1:50816/health", () => {});
|
|
@@ -107,10 +85,10 @@ async function isPortOpen() {
|
|
|
107
85
|
});
|
|
108
86
|
}
|
|
109
87
|
|
|
88
|
+
// ─── 2. 启动服务(前台黑框) ────────────────────────────────
|
|
89
|
+
|
|
110
90
|
async function startServer() {
|
|
111
91
|
const serverPath = path.join(__dirname, "server.mjs");
|
|
112
|
-
|
|
113
|
-
// 关掉旧的 Pan Router
|
|
114
92
|
try {
|
|
115
93
|
if (process.platform === "win32") {
|
|
116
94
|
execSync('taskkill /f /fi "WINDOWTITLE eq Pan Router*" >nul 2>&1', { stdio: "pipe" });
|
|
@@ -120,7 +98,6 @@ async function startServer() {
|
|
|
120
98
|
} catch {}
|
|
121
99
|
|
|
122
100
|
log("..", "正在启动 Pan Router(端口 50816)...", "yellow");
|
|
123
|
-
|
|
124
101
|
if (process.platform === "win32") {
|
|
125
102
|
execSync(`start "Pan Router" cmd /c "node ${serverPath} & pause"`, { stdio: "pipe" });
|
|
126
103
|
} else {
|
|
@@ -128,79 +105,71 @@ async function startServer() {
|
|
|
128
105
|
child.unref();
|
|
129
106
|
}
|
|
130
107
|
|
|
131
|
-
// 等待服务启动
|
|
132
108
|
for (let i = 0; i < 15; i++) {
|
|
133
|
-
if (await isPortOpen())
|
|
109
|
+
if (await isPortOpen()) break;
|
|
134
110
|
await new Promise(rs => setTimeout(rs, 1000));
|
|
135
111
|
}
|
|
136
|
-
|
|
137
|
-
log("OK", "Pan Router 已启动(端口 50816)", "green");
|
|
138
|
-
console.log("\n 现在可以运行: \x1b[33mclaude \"你好\"\x1b[0m\n");
|
|
112
|
+
log("OK", "Pan Router 已启动!现在可以运行 claude 了", "green");
|
|
139
113
|
}
|
|
140
114
|
|
|
141
|
-
// ───
|
|
115
|
+
// ─── 3. 托盘模式(后台静默) ────────────────────────────────
|
|
142
116
|
|
|
143
|
-
function startTray() {
|
|
117
|
+
async function startTray() {
|
|
118
|
+
const serverPath = path.join(__dirname, "server.mjs");
|
|
144
119
|
const vbsPath = path.join(__dirname, "panrouter-tray.vbs");
|
|
145
120
|
|
|
146
|
-
|
|
147
|
-
log("!!", "未找到 panrouter-tray.vbs", "red");
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
log("..", "正在以托盘模式启动 Pan Router...", "yellow");
|
|
121
|
+
log("..", "正在启动代理并加载托盘...", "yellow");
|
|
152
122
|
|
|
153
|
-
//
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
shell: false,
|
|
123
|
+
// 【核心修复1】:由 Node 主进程直接启动 Server,确保 API 百分百可用
|
|
124
|
+
const srv = spawn(process.execPath, [serverPath], {
|
|
125
|
+
cwd: __dirname,
|
|
126
|
+
stdio: "ignore",
|
|
127
|
+
detached: true,
|
|
128
|
+
windowsHide: true
|
|
160
129
|
});
|
|
161
|
-
|
|
130
|
+
srv.unref();
|
|
131
|
+
|
|
132
|
+
// 【核心修复2】:将 Node 的绝对路径通过环境变量塞给 VBS/PowerShell
|
|
133
|
+
process.env.PANROUTER_NODE_PATH = process.execPath;
|
|
134
|
+
|
|
135
|
+
// 启动托盘 UI
|
|
136
|
+
if (fs.existsSync(vbsPath)) {
|
|
137
|
+
const tray = spawn("wscript.exe", ["//B", "//NoLogo", vbsPath], {
|
|
138
|
+
cwd: __dirname,
|
|
139
|
+
stdio: "ignore",
|
|
140
|
+
windowsHide: true,
|
|
141
|
+
detached: true,
|
|
142
|
+
env: process.env // 传递环境变量
|
|
143
|
+
});
|
|
144
|
+
tray.unref();
|
|
145
|
+
}
|
|
162
146
|
|
|
163
|
-
|
|
164
|
-
|
|
147
|
+
// 验证端口
|
|
148
|
+
let ok = false;
|
|
149
|
+
for (let i = 0; i < 15; i++) {
|
|
150
|
+
if (await isPortOpen()) { ok = true; break; }
|
|
151
|
+
await new Promise(rs => setTimeout(rs, 1000));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (ok) {
|
|
155
|
+
log("OK", "Pan Router 代理已在后台运行 (端口: 50816)!", "green");
|
|
156
|
+
console.log(" 托盘图标应该已出现。如果系统原因导致图标没出来,代理依然可用。");
|
|
157
|
+
} else {
|
|
158
|
+
log("!!", "代理启动超时,请检查端口 50816 是否被占用", "red");
|
|
159
|
+
}
|
|
165
160
|
}
|
|
166
161
|
|
|
167
|
-
// ─── 主流程
|
|
162
|
+
// ─── 主流程 ──────────────────────────────────────────────────
|
|
168
163
|
|
|
169
164
|
function printBanner() {
|
|
170
|
-
console.log(`
|
|
171
|
-
\x1b[36m============================================\x1b[0m
|
|
172
|
-
\x1b[36m Pan Router - Claude Code 一键安装\x1b[0m
|
|
173
|
-
\x1b[36m 无需 API Key,开箱即用\x1b[0m
|
|
174
|
-
\x1b[36m============================================\x1b[0m
|
|
175
|
-
`);
|
|
165
|
+
console.log(`\n\x1b[36m=== Pan Router - Claude Code 代理 ===\x1b[0m\n`);
|
|
176
166
|
}
|
|
177
167
|
|
|
178
168
|
async function main() {
|
|
179
169
|
const args = process.argv.slice(2);
|
|
180
170
|
|
|
181
171
|
if (args.includes("--help") || args.includes("-h")) {
|
|
182
|
-
console.log(
|
|
183
|
-
pan-router — Claude Code 免费 AI 路由代理
|
|
184
|
-
|
|
185
|
-
\x1b[33m用法:\x1b[0m
|
|
186
|
-
panrouter 一键安装 + 前台启动
|
|
187
|
-
panrouter --install 只安装配置
|
|
188
|
-
panrouter --server 只启动代理(前台窗口)
|
|
189
|
-
panrouter --tray 以托盘模式启动(右下角隐藏图标)
|
|
190
|
-
panrouter --tray-install 安装配置 + 托盘启动
|
|
191
|
-
panrouter --help 显示帮助
|
|
192
|
-
|
|
193
|
-
\x1b[33m托盘模式:\x1b[0m
|
|
194
|
-
在系统通知区(右下角)显示图标,右键菜单:
|
|
195
|
-
- 启动/重启服务
|
|
196
|
-
- 开关开机自启动
|
|
197
|
-
- 退出关闭服务器和托盘
|
|
198
|
-
左键点击图标显示菜单
|
|
199
|
-
|
|
200
|
-
\x1b[33m配置:\x1b[0m
|
|
201
|
-
代理运行在 http://127.0.0.1:50816
|
|
202
|
-
Claude Code 自动使用,无需额外设置
|
|
203
|
-
`);
|
|
172
|
+
console.log("用法:\n panrouter --server (带命令行窗口运行)\n panrouter --tray (后台隐藏运行 + 托盘)\n panrouter --tray-install (安装配置 + 托盘)");
|
|
204
173
|
return;
|
|
205
174
|
}
|
|
206
175
|
|
|
@@ -208,7 +177,6 @@ async function main() {
|
|
|
208
177
|
printBanner();
|
|
209
178
|
if (!installClaudeCode()) process.exit(1);
|
|
210
179
|
writeConfig();
|
|
211
|
-
console.log("\n ✓ 安装完成,运行 \x1b[33mpanrouter --server\x1b[0m 启动代理\n");
|
|
212
180
|
return;
|
|
213
181
|
}
|
|
214
182
|
|
|
@@ -218,7 +186,7 @@ async function main() {
|
|
|
218
186
|
}
|
|
219
187
|
|
|
220
188
|
if (args.includes("--tray") || args.includes("-t")) {
|
|
221
|
-
startTray();
|
|
189
|
+
await startTray();
|
|
222
190
|
return;
|
|
223
191
|
}
|
|
224
192
|
|
|
@@ -226,11 +194,10 @@ async function main() {
|
|
|
226
194
|
printBanner();
|
|
227
195
|
if (!installClaudeCode()) process.exit(1);
|
|
228
196
|
writeConfig();
|
|
229
|
-
startTray();
|
|
197
|
+
await startTray();
|
|
230
198
|
return;
|
|
231
199
|
}
|
|
232
200
|
|
|
233
|
-
// 默认:全流程
|
|
234
201
|
printBanner();
|
|
235
202
|
if (!installClaudeCode()) process.exit(1);
|
|
236
203
|
writeConfig();
|
package/package.json
CHANGED
package/tray-daemon.ps1
CHANGED
|
@@ -1,72 +1,100 @@
|
|
|
1
1
|
<#
|
|
2
2
|
.SYNOPSIS
|
|
3
|
-
Pan Router 托盘守护脚本
|
|
4
|
-
去除了导致 UI 假死的同步网络请求,增加了稳定的重试和容错机制。
|
|
3
|
+
Pan Router 托盘守护脚本 (终极稳固版)
|
|
5
4
|
#>
|
|
6
5
|
|
|
7
|
-
Add-Type -AssemblyName System.Windows.Forms
|
|
8
|
-
Add-Type -AssemblyName System.Drawing
|
|
9
|
-
|
|
10
|
-
# 抑制全局报错弹窗,防止后台服务因为意外报错直接死掉
|
|
11
6
|
$ErrorActionPreference = "SilentlyContinue"
|
|
12
7
|
|
|
8
|
+
# 增加调试日志功能(记录在系统临时目录,遇到问题能查原因)
|
|
9
|
+
$logFile = "$env:TEMP\panrouter_tray_debug.log"
|
|
10
|
+
"--- $(Get-Date -Format 'HH:mm:ss') Tray Script Started ---" | Out-File $logFile -Append
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
14
|
+
Add-Type -AssemblyName System.Drawing
|
|
15
|
+
} catch {
|
|
16
|
+
"Failed to load assemblies: $($_.Exception.Message)" | Out-File $logFile -Append
|
|
17
|
+
Exit
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
$scriptPath = $MyInvocation.MyCommand.Path
|
|
14
21
|
$scriptDir = Split-Path $scriptPath -Parent
|
|
15
22
|
$serverPath = Join-Path $scriptDir "server.mjs"
|
|
16
|
-
$nodePath = (Get-Command node -ErrorAction SilentlyContinue).Source
|
|
17
23
|
|
|
18
|
-
#
|
|
19
|
-
|
|
24
|
+
# 接收从 Node CLI 传来的绝对路径,如果没有则降级使用 "node"
|
|
25
|
+
$nodePath = $env:PANROUTER_NODE_PATH
|
|
26
|
+
if ([string]::IsNullOrWhiteSpace($nodePath)) {
|
|
27
|
+
$nodePath = "node"
|
|
28
|
+
}
|
|
29
|
+
"Node Path identified as: $nodePath" | Out-File $logFile -Append
|
|
20
30
|
|
|
21
|
-
# ───
|
|
31
|
+
# ─── 定义重启服务的逻辑 ────────────────────────────────────
|
|
22
32
|
function Restart-Server {
|
|
23
|
-
|
|
33
|
+
"Executing Restart-Server..." | Out-File $logFile -Append
|
|
24
34
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
}
|
|
35
|
+
# 使用更为稳健的 WMI 查询关闭进程
|
|
36
|
+
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" |
|
|
37
|
+
Where-Object { $_.CommandLine -match "server\.mjs" } |
|
|
38
|
+
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
30
39
|
} catch {}
|
|
31
40
|
|
|
32
|
-
# 无黑框启动新进程
|
|
33
41
|
try {
|
|
34
|
-
Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden
|
|
35
|
-
|
|
42
|
+
Start-Process -FilePath $nodePath -ArgumentList "`"$serverPath`"" -WorkingDirectory $scriptDir -WindowStyle Hidden
|
|
43
|
+
"Server process spawned successfully." | Out-File $logFile -Append
|
|
44
|
+
} catch {
|
|
45
|
+
"Server start error: $($_.Exception.Message)" | Out-File $logFile -Append
|
|
46
|
+
}
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
#
|
|
39
|
-
|
|
49
|
+
# ─── 保证服务在运行(用于应对开机自启动的情况) ───────────
|
|
50
|
+
$isRunning = $false
|
|
51
|
+
try {
|
|
52
|
+
$procs = Get-WmiObject Win32_Process -Filter "Name = 'node.exe'"
|
|
53
|
+
foreach ($p in $procs) {
|
|
54
|
+
if ($p.CommandLine -match "server\.mjs") {
|
|
55
|
+
$isRunning = $true
|
|
56
|
+
break
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch {}
|
|
40
60
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
$g.Clear([System.Drawing.Color]::Transparent)
|
|
46
|
-
$brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
|
|
47
|
-
$g.FillEllipse($brush, 0, 0, 15, 15)
|
|
48
|
-
$font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
|
|
49
|
-
$fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
50
|
-
$g.DrawString("P", $font, $fg, 3, 1.5)
|
|
61
|
+
if (-not $isRunning) {
|
|
62
|
+
"Server not running on load, starting..." | Out-File $logFile -Append
|
|
63
|
+
Restart-Server
|
|
64
|
+
}
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
# ─── 绘制托盘图标 ──────────────────────────────────────────
|
|
67
|
+
try {
|
|
68
|
+
$bmp = New-Object System.Drawing.Bitmap(16, 16)
|
|
69
|
+
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
70
|
+
$g.SmoothingMode = 'HighQuality'
|
|
71
|
+
$g.Clear([System.Drawing.Color]::Transparent)
|
|
72
|
+
$brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
|
|
73
|
+
$g.FillEllipse($brush, 0, 0, 15, 15)
|
|
74
|
+
$font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Bold)
|
|
75
|
+
$fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
|
76
|
+
$g.DrawString("P", $font, $fg, 3, 2)
|
|
77
|
+
|
|
78
|
+
$hIcon = $bmp.GetHicon()
|
|
79
|
+
$icon = [System.Drawing.Icon]::FromHandle($hIcon)
|
|
80
|
+
} catch {
|
|
81
|
+
"Icon drawing error: $($_.Exception.Message)" | Out-File $logFile -Append
|
|
82
|
+
Exit
|
|
83
|
+
}
|
|
54
84
|
|
|
55
|
-
# ───
|
|
85
|
+
# ─── 初始化托盘菜单 ────────────────────────────────────────
|
|
56
86
|
$notifyIcon = New-Object System.Windows.Forms.NotifyIcon
|
|
57
87
|
$notifyIcon.Icon = $icon
|
|
58
88
|
$notifyIcon.Text = "Pan Router (端口: 50816)"
|
|
59
89
|
|
|
60
90
|
$menu = New-Object System.Windows.Forms.ContextMenuStrip
|
|
61
91
|
|
|
62
|
-
# 标题 (不可点)
|
|
63
92
|
$titleItem = New-Object System.Windows.Forms.ToolStripMenuItem("Pan Router | :50816")
|
|
64
93
|
$titleItem.Enabled = $false
|
|
65
94
|
$titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
|
|
66
95
|
$menu.Items.Add($titleItem) | Out-Null
|
|
67
96
|
$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
|
|
68
97
|
|
|
69
|
-
# 重启服务选项
|
|
70
98
|
$restartItem = New-Object System.Windows.Forms.ToolStripMenuItem("重启后台服务")
|
|
71
99
|
$restartItem.Add_Click({
|
|
72
100
|
Restart-Server
|
|
@@ -74,10 +102,9 @@ $restartItem.Add_Click({
|
|
|
74
102
|
})
|
|
75
103
|
$menu.Items.Add($restartItem) | Out-Null
|
|
76
104
|
|
|
77
|
-
# 开机自启动选项
|
|
78
105
|
$autoItem = New-Object System.Windows.Forms.ToolStripMenuItem("开机自启动")
|
|
79
106
|
try {
|
|
80
|
-
$regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter
|
|
107
|
+
$regKey = Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name PanRouter
|
|
81
108
|
$autoItem.Checked = ($regKey -ne $null)
|
|
82
109
|
} catch {}
|
|
83
110
|
|
|
@@ -95,39 +122,32 @@ $autoItem.Add_Click({
|
|
|
95
122
|
$menu.Items.Add($autoItem) | Out-Null
|
|
96
123
|
$menu.Items.Add((New-Object System.Windows.Forms.ToolStripSeparator)) | Out-Null
|
|
97
124
|
|
|
98
|
-
# 退出选项
|
|
99
125
|
$exitItem = New-Object System.Windows.Forms.ToolStripMenuItem("退出")
|
|
100
126
|
$exitItem.Add_Click({
|
|
101
127
|
$notifyIcon.Visible = $false
|
|
102
|
-
try {
|
|
128
|
+
try {
|
|
129
|
+
Get-WmiObject Win32_Process -Filter "Name = 'node.exe'" | Where-Object { $_.CommandLine -match "server\.mjs" } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
130
|
+
} catch {}
|
|
103
131
|
[System.Windows.Forms.Application]::Exit()
|
|
104
132
|
})
|
|
105
133
|
$menu.Items.Add($exitItem) | Out-Null
|
|
106
134
|
|
|
107
135
|
$notifyIcon.ContextMenuStrip = $menu
|
|
108
136
|
|
|
109
|
-
# ─── 优化左键操作 ────────────────────────────────────
|
|
110
137
|
$notifyIcon.Add_MouseClick({
|
|
111
138
|
if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
|
|
112
|
-
# 利用反射直接呼出右键菜单,取消容易卡死的网络检查
|
|
113
139
|
$mi = [System.Windows.Forms.NotifyIcon].GetMethod("ShowContextMenu", [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance)
|
|
114
140
|
$mi.Invoke($notifyIcon, $null)
|
|
115
141
|
}
|
|
116
142
|
})
|
|
117
143
|
|
|
118
|
-
# ─── 启动与保持运行 ──────────────────────────────────
|
|
119
144
|
$notifyIcon.Visible = $true
|
|
120
145
|
|
|
121
|
-
|
|
146
|
+
"UI setup completed, entering ApplicationContext loop." | Out-File $logFile -Append
|
|
147
|
+
|
|
122
148
|
$appContext = New-Object System.Windows.Forms.ApplicationContext
|
|
123
149
|
[System.Windows.Forms.Application]::Run($appContext)
|
|
124
150
|
|
|
125
|
-
|
|
151
|
+
"Exiting..." | Out-File $logFile -Append
|
|
126
152
|
$notifyIcon.Visible = $false
|
|
127
153
|
$notifyIcon.Dispose()
|
|
128
|
-
$font.Dispose()
|
|
129
|
-
$fg.Dispose()
|
|
130
|
-
$brush.Dispose()
|
|
131
|
-
$g.Dispose()
|
|
132
|
-
[System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
|
|
133
|
-
$icon.Dispose()
|