panrouter 1.1.1 → 1.2.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 CHANGED
@@ -138,30 +138,39 @@ async function startServer() {
138
138
  console.log("\n 现在可以运行: \x1b[33mclaude \"你好\"\x1b[0m\n");
139
139
  }
140
140
 
141
- // ─── 4. 以托盘模式启动(隐藏窗口 + 系统托盘图标) ────────────────────────
141
+ // ─── 4. 以托盘模式启动(后台守护进程 + 系统托盘) ──────────────────────────
142
142
 
143
+ /**
144
+ * 启动策略:
145
+ * 1. 启动 daemon.mjs (detached, 无窗口)
146
+ * → daemon 启动隐藏的 server.mjs
147
+ * → daemon 启动 tray-manager.ps1 (pipe IPC)
148
+ * → daemon 保持存活直到用户点"退出"
149
+ * 2. cli.mjs 进程立刻退出
150
+ */
143
151
  function startTray() {
144
- const serverPath = path.join(__dirname, "server.mjs");
145
- const trayScript = path.join(__dirname, "tray-manager.ps1");
152
+ const daemonPath = path.join(__dirname, "daemon.mjs");
146
153
 
147
- if (!fs.existsSync(trayScript)) {
148
- log("!!", "未找到 tray-manager.ps1", "red");
154
+ if (!fs.existsSync(daemonPath)) {
155
+ log("!!", "未找到 daemon.mjs", "red");
149
156
  process.exit(1);
150
157
  }
151
158
 
152
159
  log("..", "正在以托盘模式启动 Pan Router...", "yellow");
153
160
 
154
- // 无窗口运行托盘管理器
155
- const child = spawn("powershell", [
156
- "-ExecutionPolicy", "Bypass",
157
- "-WindowStyle", "Hidden",
158
- "-STA",
159
- "-File", trayScript,
160
- "-ServerPath", serverPath,
161
+ const serverPath = path.join(__dirname, "server.mjs");
162
+ const trayPath = path.join(__dirname, "tray-manager.ps1");
163
+
164
+ const child = spawn(process.execPath, [
165
+ daemonPath,
166
+ `--serverPath="${serverPath}"`,
167
+ `--trayPath="${trayPath}"`,
161
168
  ], {
162
169
  cwd: __dirname,
163
170
  stdio: "ignore",
164
171
  detached: true,
172
+ windowsHide: true,
173
+ shell: false,
165
174
  });
166
175
  child.unref();
167
176
 
package/daemon.mjs ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pan Router Daemon — 后台守护进程
5
+ *
6
+ * 由 cli.mjs 以 --tray 参数启动 (detached, hidden)。
7
+ * 管理 server.mjs + tray-manager.ps1, 通过 stdin/stdout JSON 通信。
8
+ *
9
+ * 用法(由 cli.mjs 调用):
10
+ * node daemon.mjs --serverPath="..." --trayPath="..."
11
+ */
12
+
13
+ import { spawn, execSync } from "node:child_process";
14
+ import { createInterface } from "node:readline";
15
+ import { fileURLToPath } from "node:url";
16
+ import path from "node:path";
17
+ import fs from "node:fs";
18
+ import http from "node:http";
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+
22
+ // ─── 解析参数 ────────────────────────────────────
23
+ const serverPath = process.argv.find(a => a.startsWith("--serverPath="))?.split("=")[1];
24
+ const trayPath = process.argv.find(a => a.startsWith("--trayPath="))?.split("=")[1];
25
+ if (!serverPath || !trayPath) { process.exit(1); }
26
+
27
+ const appDir = path.dirname(serverPath);
28
+ const AUTOSTART_NAME = "PanRouter";
29
+ const RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
30
+
31
+ let serverProcess = null;
32
+ let psProcess = null;
33
+
34
+ // ─── 日志 ────────────────────────────────────────
35
+ const LOG = path.join(process.env.TEMP || "/tmp", "panrouter-daemon.log");
36
+ function log(msg) {
37
+ try { fs.appendFileSync(LOG, `${new Date().toISOString().slice(11,19)} ${msg}\n`); } catch {}
38
+ }
39
+
40
+ // ─── 健康检查 ─────────────────────────────────────
41
+ function isOnline() {
42
+ return new Promise(rs => {
43
+ const req = http.get("http://127.0.0.1:50816/health", () => {});
44
+ req.on("response", () => { req.destroy(); rs(true); });
45
+ req.on("error", () => rs(false));
46
+ req.setTimeout(1500, () => { req.destroy(); rs(false); });
47
+ });
48
+ }
49
+
50
+ // ─── 杀掉旧 server 进程 ──────────────────────────
51
+ function killOldServers() {
52
+ try {
53
+ const wmic = spawn("wmic", [
54
+ "process", "where", "name='node.exe'", "get", "ProcessId,CommandLine", "/format:csv"
55
+ ], { stdio: ["ignore", "pipe", "ignore"] });
56
+ let out = "";
57
+ wmic.stdout.on("data", d => out += d.toString());
58
+ wmic.on("close", () => {
59
+ for (const line of out.split("\n")) {
60
+ if (line.includes("server.mjs")) {
61
+ const m = line.match(/(\d+),.*?server\.mjs/);
62
+ if (m) { try { process.kill(parseInt(m[1]), "SIGKILL"); } catch {} }
63
+ }
64
+ }
65
+ });
66
+ } catch {}
67
+ }
68
+
69
+ // ─── 隐藏启动 server.mjs ─────────────────────────
70
+ function startServer() {
71
+ killOldServers();
72
+ serverProcess = spawn("node", [serverPath], {
73
+ cwd: appDir, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, shell: false,
74
+ });
75
+ serverProcess.stdout.on("data", d => log("[srv] " + d.toString().trim()));
76
+ serverProcess.stderr.on("data", d => log("[srv-err] " + d.toString().trim()));
77
+ serverProcess.on("exit", code => log(`Server exited code=${code}`));
78
+ log(`Server started PID=${serverProcess.pid}`);
79
+ }
80
+
81
+ // ─── 命令 → PS 托盘 ─────────────────────────────
82
+ function psSend(cmd) {
83
+ if (psProcess?.stdin?.writable) {
84
+ psProcess.stdin.write(JSON.stringify(cmd) + "\n");
85
+ }
86
+ }
87
+
88
+ // ─── 开机自启动操作 (reg.exe) ────────────────────
89
+ function toggleAutostart() {
90
+ try {
91
+ const out = execSync(`reg query ${RUN_KEY} /v ${AUTOSTART_NAME} 2>nul`, {
92
+ encoding: "utf8", windowsHide: true, timeout: 3000,
93
+ });
94
+ const isOn = !out.toLowerCase().includes("error");
95
+ if (isOn) {
96
+ execSync(`reg delete ${RUN_KEY} /v ${AUTOSTART_NAME} /f 2>nul`, { windowsHide: true, timeout: 3000 });
97
+ psSend({ action: "update-item", index: 2, title: "开机自启动", enabled: true });
98
+ log("Autostart OFF");
99
+ } else {
100
+ const exe = process.execPath;
101
+ const daemon = path.join(__dirname, "daemon.mjs");
102
+ const cmd = `"${exe}" "${daemon}" --serverPath="${serverPath}" --trayPath="${trayPath}"`;
103
+ execSync(`reg add ${RUN_KEY} /v ${AUTOSTART_NAME} /t REG_SZ /d "${cmd}" /f 2>nul`, { windowsHide: true, timeout: 3000 });
104
+ psSend({ action: "update-item", index: 2, title: "✓ 开机自启动", enabled: true });
105
+ log("Autostart ON");
106
+ }
107
+ } catch (e) { log(`Autostart error: ${e.message}`); }
108
+ }
109
+
110
+ // ─── 退出清理 ────────────────────────────────────
111
+ function cleanup() {
112
+ log("Cleanup...");
113
+ try { if (serverProcess && !serverProcess.killed) serverProcess.kill("SIGKILL"); } catch {}
114
+ killOldServers();
115
+ }
116
+
117
+ // ═══════════════ 主流程 ═══════════════
118
+
119
+ log("=== PanRouter Daemon ===");
120
+ log(`serverPath=${serverPath}`);
121
+ log(`trayPath=${trayPath}`);
122
+
123
+ // 1. 启动服务器
124
+ startServer();
125
+
126
+ // 2. 等服务器就绪, 启动托盘
127
+ (async () => {
128
+ let ready = false;
129
+ for (let i = 0; i < 20; i++) {
130
+ if (await isOnline()) { ready = true; break; }
131
+ await new Promise(r => setTimeout(r, 500));
132
+ }
133
+ log(`Server ready=${ready}`);
134
+
135
+ // 3. 启动 PS 托盘 (pipe 连接, 保持 IPC)
136
+ psProcess = spawn("powershell.exe", [
137
+ "-NoProfile", "-ExecutionPolicy", "Bypass",
138
+ "-WindowStyle", "Hidden",
139
+ "-File", trayPath,
140
+ ], {
141
+ stdio: ["pipe", "pipe", "pipe"], windowsHide: true, shell: false,
142
+ });
143
+
144
+ // 读取 PS 事件
145
+ createInterface({ input: psProcess.stdout }).on("line", (line) => {
146
+ try {
147
+ const evt = JSON.parse(line);
148
+ log(`PS: ${line}`);
149
+ if (evt.type === "started") {
150
+ // 发送菜单配置
151
+ psSend({ action: "add-item", index: 0, title: "Pan Router - :50816", enabled: false });
152
+ psSend({ action: "add-item", index: 1, title: "─".repeat(19), enabled: false });
153
+ psSend({ action: "add-item", index: 2, title: "开机自启动", enabled: true });
154
+ psSend({ action: "add-item", index: 3, title: "─".repeat(19), enabled: false });
155
+ psSend({ action: "add-item", index: 4, title: "退出", enabled: true });
156
+ psSend({ action: "set-tooltip", text: "Pan Router | 端口 50816" });
157
+ log("Menu configured");
158
+ }
159
+ if (evt.type === "click" && evt.index === 2) toggleAutostart();
160
+ if (evt.type === "click" && evt.index === 4) {
161
+ log("Exit requested");
162
+ cleanup();
163
+ psSend({ action: "kill" });
164
+ setTimeout(() => process.exit(0), 500);
165
+ }
166
+ } catch (e) { log(`PS parse error: ${e.message}`); }
167
+ });
168
+
169
+ psProcess.on("error", err => log(`PS error: ${err.message}`));
170
+ psProcess.stderr.on("data", d => log(`[ps-err] ${d.toString().trim()}`));
171
+ psProcess.on("exit", code => { log(`PS exited code=${code}`); process.exit(0); });
172
+
173
+ log("Daemon ready");
174
+ process.stdin.resume(); // 保持进程存活
175
+ })();
176
+
177
+ process.on("SIGTERM", () => { cleanup(); setTimeout(() => process.exit(0), 300); });
178
+ process.on("SIGINT", () => { cleanup(); setTimeout(() => process.exit(0), 300); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "cli.mjs",
11
+ "daemon.mjs",
11
12
  "server.mjs",
12
13
  "tray-manager.ps1"
13
14
  ],
package/tray-manager.ps1 CHANGED
@@ -1,223 +1,111 @@
1
1
  <#
2
2
  .SYNOPSIS
3
- Pan Router 系统托盘管理器
3
+ Pan Router 托盘管理器 (IPC 版)
4
4
  .DESCRIPTION
5
- 在通知区域(右下角)显示图标,隐藏命令窗口。
6
- 右键菜单: 状态, 开机自启动开关, 退出
7
-
8
- 用法:
9
- powershell -ExecutionPolicy Bypass -STA -File tray-manager.ps1 -ServerPath "server.mjs"
5
+ 纯 NotifyIcon 包装器, 通过 stdin/stdout JSON 与父进程通信
6
+
7
+ 支持命令 (stdin):
8
+ {"action":"add-item","index":0,"title":"...","enabled":true}
9
+ {"action":"update-item","index":0,"title":"...","enabled":true}
10
+ {"action":"set-tooltip","text":"..."}
11
+ {"action":"kill"}
12
+
13
+ 事件 (stdout):
14
+ {"type":"started"}
15
+ {"type":"ready"} <- PS 就绪 + STA 已确认
16
+ {"type":"click","index":0}
17
+ {"type":"error","message":"..."}
10
18
  #>
11
19
 
12
- param(
13
- [string]$ServerPath
14
- )
15
-
16
- # ─── 日志辅助 ──────────────────────────────────
17
- $logFile = "$env:TEMP\panrouter-tray.log"
18
- function Write-Log { param([string]$m) "$([DateTime]::Now.ToString('HH:mm:ss')) $m" | Out-File -Append -Encoding utf8 $logFile }
19
-
20
- try {
21
- Write-Log "==== Pan Router Tray Started ===="
22
- Write-Log "ServerPath: $ServerPath"
23
- Write-Log "PSVersion: $($PSVersionTable.PSVersion)"
24
-
25
- Add-Type -AssemblyName System.Windows.Forms
26
- Add-Type -AssemblyName System.Drawing
20
+ param()
27
21
 
28
- # ─── 常量 ──────────────────────────────────
29
- $scriptPath = $MyInvocation.MyCommand.Path
30
- $autostartName = "PanRouter"
31
- $runKeyPath = "Software\Microsoft\Windows\CurrentVersion\Run"
22
+ Add-Type -AssemblyName System.Windows.Forms
23
+ Add-Type -AssemblyName System.Drawing
32
24
 
33
- # ─── 生成图标 ──────────────────────────────
34
- $bmp = New-Object System.Drawing.Bitmap(16, 16)
35
- $g = [System.Drawing.Graphics]::FromImage($bmp)
36
- $g.SmoothingMode = 'HighQuality'
37
- $g.Clear([System.Drawing.Color]::Transparent)
38
- $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
39
- $g.FillEllipse($brush, 0, 0, 15, 15)
40
- $font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
41
- $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
42
- $g.DrawString("P", $font, $fg, 3, 1.5)
43
- $font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
44
- $hIcon = $bmp.GetHicon()
45
- $icon = [System.Drawing.Icon]::FromHandle($hIcon)
46
- $bmp.Dispose()
47
- Write-Log "Icon created"
25
+ $ErrorActionPreference = "Stop"
26
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
48
27
 
49
- # ─── NotifyIcon ────────────────────────────
50
- $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
51
- $notifyIcon.Icon = $icon
52
- $notifyIcon.Text = "Pan Router`n端口 50816 | 运行中"
53
- $notifyIcon.Visible = $true
54
- Write-Log "NotifyIcon created and visible"
28
+ $script:notifyIcon = New-Object System.Windows.Forms.NotifyIcon
29
+ $script:notifyIcon.Visible = $true
30
+ $script:menu = New-Object System.Windows.Forms.ContextMenuStrip
31
+ $script:notifyIcon.ContextMenuStrip = $script:menu
32
+ $script:items = @()
55
33
 
56
- # ─── 用 UseShellExecute 启动服务器(避免输出缓冲死锁) ──
57
- function Start-ServerNohup {
58
- param([string]$ServerPath)
59
- $serverDir = Split-Path $ServerPath -Parent
60
- try {
61
- $psi = New-Object System.Diagnostics.ProcessStartInfo
62
- $psi.FileName = "node"
63
- $psi.Arguments = "`"$ServerPath`""
64
- $psi.WorkingDirectory = $serverDir
65
- $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
66
- $psi.CreateNoWindow = $true
67
- # UseShellExecute=$true 避免重定向缓冲死锁
68
- $psi.UseShellExecute = $true
69
- $proc = [System.Diagnostics.Process]::Start($psi)
70
- Write-Log "Server started PID=$($proc.Id)"
71
- return $proc
72
- } catch {
73
- Write-Log "Server start FAILED: $_"
74
- return $null
75
- }
76
- }
77
-
78
- # ─── 杀掉旧进程 (wmic 兼容 PS5.1) ──────────
79
- function Stop-OldServer {
80
- try {
81
- $old = Get-WmiObject Win32_Process -Filter "Name='node.exe'" -ErrorAction Stop |
82
- Where-Object { $_.CommandLine -match "server\.mjs" }
83
- foreach ($p in $old) {
84
- Write-Log "Killing old server PID=$($p.ProcessId)"
85
- Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
86
- }
87
- } catch { Write-Log "Stop-OldServer: $_" }
88
- }
89
-
90
- # ─── 健康检查 ──────────────────────────────
91
- function Test-Online {
92
- try {
93
- $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health")
94
- $req.Timeout = 1500
95
- $resp = $req.GetResponse()
96
- $resp.Close()
97
- return $true
98
- } catch { return $false }
99
- }
34
+ function Write-Event($obj) {
35
+ $json = $obj | ConvertTo-Json -Compress
36
+ try {
37
+ [Console]::Out.WriteLine($json)
38
+ [Console]::Out.Flush()
39
+ } catch {}
40
+ }
100
41
 
101
- # ─── 启动流程 ──────────────────────────────
102
- Stop-OldServer
103
- $serverProcess = Start-ServerNohup -ServerPath $ServerPath
42
+ # ─── 生成蓝色 P 图标 (纯内存, 无需 .ico 文件) ──
43
+ $bmp = New-Object System.Drawing.Bitmap(16, 16)
44
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
45
+ $g.SmoothingMode = 'HighQuality'
46
+ $g.Clear([System.Drawing.Color]::Transparent)
47
+ $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
48
+ $g.FillEllipse($brush, 0, 0, 15, 15)
49
+ $font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
50
+ $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
51
+ $g.DrawString("P", $font, $fg, 3, 1.5)
52
+ $font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
53
+ $hIcon = $bmp.GetHicon()
54
+ $icon = [System.Drawing.Icon]::FromHandle($hIcon)
55
+ $bmp.Dispose()
56
+
57
+ $script:notifyIcon.Icon = $icon
58
+ $script:notifyIcon.Text = "Pan Router"
59
+
60
+ function Add-MenuItem($index, $title, $enabled) {
61
+ $item = New-Object System.Windows.Forms.ToolStripMenuItem
62
+ $item.Text = $title
63
+ $item.Enabled = $enabled
64
+ $idx = $index
65
+ $item.Add_Click({ Write-Event @{type="click"; index=$idx} }.GetNewClosure())
66
+ $script:menu.Items.Add($item) | Out-Null
67
+ $script:items += $item
68
+ }
104
69
 
105
- # 前台等待服务器就绪(超时 10 秒)
106
- $ready = $false
107
- for ($i = 0; $i -lt 20; $i++) {
108
- Start-Sleep -Milliseconds 500
109
- if (Test-Online) { $ready = $true; break }
110
- }
111
- Write-Log "Server ready=$ready"
112
- if ($ready) {
113
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
70
+ function Update-MenuItem($index, $title, $enabled) {
71
+ if ($index -lt $script:items.Count) {
72
+ $script:items[$index].Text = $title
73
+ $script:items[$index].Enabled = $enabled
114
74
  }
75
+ }
115
76
 
116
- # ─── 后台定时健康检查 ──────────────────────
117
- $healthTimer = New-Object System.Windows.Forms.Timer
118
- $healthTimer.Interval = 30000
119
- $healthTimer.Add_Tick({
120
- if (-not (Test-Online)) {
121
- Write-Log "Health check FAILED, restarting..."
122
- Stop-OldServer
123
- $script:serverProcess = Start-ServerNohup -ServerPath $ServerPath
124
- Start-Sleep -Seconds 3
125
- if (Test-Online) {
126
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已自动重启", [System.Windows.Forms.ToolTipIcon]::Info)
127
- }
128
- }
129
- })
130
- $healthTimer.Start()
131
- Write-Log "Health timer started"
77
+ function Set-Tooltip($text) {
78
+ if ($text.Length -gt 63) { $text = $text.Substring(0, 63) }
79
+ $script:notifyIcon.Text = $text
80
+ }
132
81
 
133
- # ─── 左键: 状态气泡 ────────────────────────
134
- $notifyIcon.Add_MouseClick({
135
- if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
136
- $online = Test-Online
137
- if ($online) {
138
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "运行正常 ✓ (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
139
- } else {
140
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务器未响应 ⚠", [System.Windows.Forms.ToolTipIcon]::Error)
82
+ # ─── stdin 轮询 (100ms 间隔) ────────────────────
83
+ $script:timer = New-Object System.Windows.Forms.Timer
84
+ $script:timer.Interval = 100
85
+ $script:timer.Add_Tick({
86
+ try {
87
+ while ([Console]::In.Peek() -ne -1) {
88
+ $line = [Console]::In.ReadLine()
89
+ if ([string]::IsNullOrWhiteSpace($line)) { continue }
90
+ $cmd = $line | ConvertFrom-Json
91
+ switch ($cmd.action) {
92
+ "add-item" { Add-MenuItem $cmd.index $cmd.title $cmd.enabled }
93
+ "update-item" { Update-MenuItem $cmd.index $cmd.title $cmd.enabled }
94
+ "set-tooltip" { Set-Tooltip $cmd.text }
95
+ "kill" {
96
+ $script:notifyIcon.Visible = $false
97
+ $script:notifyIcon.Dispose()
98
+ $icon.Dispose()
99
+ [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
100
+ [System.Windows.Forms.Application]::Exit()
101
+ }
141
102
  }
142
103
  }
143
- })
144
-
145
- # ─── 右键菜单 ──────────────────────────────
146
- $menu = New-Object System.Windows.Forms.ContextMenuStrip
147
-
148
- $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem
149
- $titleItem.Text = "Pan Router - :50816"
150
- $titleItem.Enabled = $false
151
- $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.FontStyle]::Bold)
152
- $menu.Items.Add($titleItem)
153
- $menu.Items.Add("-")
154
-
155
- $autoStartItem = New-Object System.Windows.Forms.ToolStripMenuItem
156
- $autoStartItem.Text = "开机自启动"
157
- $rk = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
158
- $autoStartItem.Checked = ($rk.GetValue($autostartName) -ne $null)
159
- $rk.Close()
160
- $autoStartItem.Add_Click({
161
- $rk2 = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
162
- if ($autoStartItem.Checked) {
163
- $rk2.DeleteValue($autostartName, $false)
164
- $autoStartItem.Checked = $false
165
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
166
- Write-Log "Autostart OFF"
167
- } else {
168
- $cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$scriptPath`" -ServerPath `"$ServerPath`""
169
- $rk2.SetValue($autostartName, $cmd)
170
- $autoStartItem.Checked = $true
171
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
172
- Write-Log "Autostart ON"
173
- }
174
- $rk2.Close()
175
- })
176
- $menu.Items.Add($autoStartItem)
177
- $menu.Items.Add("-")
178
-
179
- $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem
180
- $exitItem.Text = "退出"
181
- $exitItem.Add_Click({
182
- Write-Log "Exit clicked"
183
- $notifyIcon.Visible = $false
184
- [System.Windows.Forms.Application]::Exit()
185
- })
186
- $menu.Items.Add($exitItem)
187
-
188
- $notifyIcon.ContextMenuStrip = $menu
189
-
190
- # ─── 退出清理 ──────────────────────────────
191
- $cleanup = {
192
- try {
193
- Write-Log "Cleanup started"
194
- $healthTimer.Stop()
195
- $healthTimer.Dispose()
196
- if (-not $serverProcess.HasExited) {
197
- $serverProcess.Kill()
198
- $serverProcess.WaitForExit(3000)
199
- $serverProcess.Dispose()
200
- Write-Log "Server killed"
201
- }
202
- Stop-OldServer
203
- Write-Log "Cleanup done"
204
- } catch { Write-Log "Cleanup error: $_" }
104
+ } catch {
105
+ Write-Event @{type="error"; message=$_.Exception.Message}
205
106
  }
107
+ })
108
+ $script:timer.Start()
206
109
 
207
- [System.Windows.Forms.Application]::ApplicationExit += {
208
- & $cleanup
209
- $notifyIcon.Dispose()
210
- [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
211
- $icon.Dispose()
212
- }
213
-
214
- Write-Log "Entering message loop"
215
- [System.Windows.Forms.Application]::Run()
216
- Write-Log "Message loop exited"
217
- & $cleanup
218
-
219
- } catch {
220
- $errMsg = "UNHANDLED ERROR: $_"
221
- Write-Log $errMsg
222
- try { [System.Windows.Forms.MessageBox]::Show($errMsg, "Pan Router Error") } catch {}
223
- }
110
+ Write-Event @{type="started"}
111
+ [System.Windows.Forms.Application]::Run()