panrouter 1.1.0 → 1.1.2

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.
Files changed (3) hide show
  1. package/package.json +3 -2
  2. package/tray-app.cs +324 -0
  3. package/tray-manager.ps1 +116 -155
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "panrouter",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "让 Claude Code 免费使用 DeepSeek 等模型,无需 API Key",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "cli.mjs",
11
11
  "server.mjs",
12
- "tray-manager.ps1"
12
+ "tray-manager.ps1",
13
+ "tray-app.cs"
13
14
  ],
14
15
  "license": "MIT"
15
16
  }
package/tray-app.cs ADDED
@@ -0,0 +1,324 @@
1
+ /* Pan Router Tray App
2
+ * 编译: csc /nologo /target:winexe /reference:System.Windows.Forms.dll /reference:System.Drawing.dll tray-app.cs
3
+ *
4
+ * 功能: 以系统托盘方式运行 panrouter 服务器, 无窗口
5
+ * 右键菜单: 状态, 开机自启动开关, 退出
6
+ */
7
+
8
+ using System;
9
+ using System.Diagnostics;
10
+ using System.Drawing;
11
+ using System.IO;
12
+ using System.Net;
13
+ using System.Reflection;
14
+ using System.Runtime.InteropServices;
15
+ using System.Text;
16
+ using System.Threading;
17
+ using System.Windows.Forms;
18
+
19
+ public class PanRouterTrayApp
20
+ {
21
+ private static NotifyIcon _notifyIcon;
22
+ private static Process _serverProcess;
23
+ private static string _serverPath;
24
+ private static string _appDir;
25
+ private static System.Threading.Timer _healthTimer;
26
+ private static StreamWriter _log;
27
+ private static readonly string _logPath = Path.Combine(Path.GetTempPath(), "panrouter-tray.log");
28
+
29
+ // ─── Win32 API: 隐藏窗口 ──────────────────────
30
+ [DllImport("user32.dll")]
31
+ private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
32
+ private const int SW_HIDE = 0;
33
+
34
+ [STAThread]
35
+ public static void Main(string[] args)
36
+ {
37
+ // Parse args
38
+ foreach (var a in args)
39
+ {
40
+ if (a.StartsWith("--serverPath:", StringComparison.OrdinalIgnoreCase))
41
+ _serverPath = a.Substring("--serverPath:".Length).Trim('"');
42
+ }
43
+ if (string.IsNullOrEmpty(_serverPath) || !File.Exists(_serverPath))
44
+ {
45
+ MessageBox.Show("Pan Router: 找不到 server.mjs", "Pan Router", MessageBoxButtons.OK, MessageBoxIcon.Error);
46
+ return;
47
+ }
48
+ _appDir = Path.GetDirectoryName(_serverPath);
49
+
50
+ // Open log
51
+ try
52
+ {
53
+ _log = new StreamWriter(_logPath, false, Encoding.UTF8);
54
+ _log.AutoFlush = true;
55
+ Log("=== PanRouter Tray App v1 ===");
56
+ Log("ServerPath: " + _serverPath);
57
+ }
58
+ catch { }
59
+
60
+ // Kill old servers
61
+ KillOldServers();
62
+
63
+ // Start hidden server
64
+ StartServer();
65
+
66
+ // Wait for ready
67
+ bool ready = false;
68
+ for (int i = 0; i < 20; i++)
69
+ {
70
+ Thread.Sleep(500);
71
+ if (IsOnline()) { ready = true; break; }
72
+ }
73
+ Log("Server ready=" + ready);
74
+
75
+ // Create tray icon
76
+ CreateTrayIcon();
77
+
78
+ // Show balloon
79
+ if (ready)
80
+ _notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", ToolTipIcon.Info);
81
+ else
82
+ _notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器启动异常,查看日志", ToolTipIcon.Warning);
83
+
84
+ // Health check every 30s
85
+ _healthTimer = new System.Threading.Timer(HealthCheck, null, 30000, 30000);
86
+
87
+ // Message loop
88
+ Application.Run();
89
+ }
90
+
91
+ private static void Log(string msg)
92
+ {
93
+ try { _log?.WriteLine(DateTime.Now.ToString("HH:mm:ss") + " " + msg); } catch { }
94
+ }
95
+
96
+ // ─── 杀掉旧服务器 ────────────────────────────
97
+ private static void KillOldServers()
98
+ {
99
+ try
100
+ {
101
+ // Use WMIC via command line to find node.exe processes running server.mjs
102
+ var psi = new ProcessStartInfo("wmic", "process where \"name='node.exe'\" get ProcessId,CommandLine /format:csv")
103
+ {
104
+ CreateNoWindow = true,
105
+ WindowStyle = ProcessWindowStyle.Hidden,
106
+ UseShellExecute = false,
107
+ RedirectStandardOutput = true,
108
+ RedirectStandardError = true
109
+ };
110
+ using (var p = Process.Start(psi))
111
+ {
112
+ string output = p.StandardOutput.ReadToEnd();
113
+ p.WaitForExit(2000);
114
+
115
+ foreach (string line in output.Split('\n'))
116
+ {
117
+ if (line.Contains("server.mjs"))
118
+ {
119
+ string[] parts = line.Split(',');
120
+ if (parts.Length >= 3 && int.TryParse(parts[2].Trim(), out int pid))
121
+ {
122
+ Log("Killing old server PID=" + pid);
123
+ try { Process.GetProcessById(pid)?.Kill(); } catch { }
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ catch (Exception ex) { Log("KillOld error: " + ex.Message); }
130
+ }
131
+
132
+ // ─── 启动隐藏服务器 ───────────────────────────
133
+ private static void StartServer()
134
+ {
135
+ try
136
+ {
137
+ _serverProcess = new Process();
138
+ _serverProcess.StartInfo.FileName = "node";
139
+ _serverProcess.StartInfo.Arguments = "\"" + _serverPath + "\"";
140
+ _serverProcess.StartInfo.WorkingDirectory = _appDir;
141
+ _serverProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
142
+ _serverProcess.StartInfo.CreateNoWindow = true;
143
+ _serverProcess.StartInfo.UseShellExecute = false;
144
+ // Read output so buffer never blocks
145
+ _serverProcess.StartInfo.RedirectStandardOutput = true;
146
+ _serverProcess.StartInfo.RedirectStandardError = true;
147
+ _serverProcess.OutputDataReceived += (s, e) => { if (!string.IsNullOrEmpty(e.Data)) Log("[srv] " + e.Data); };
148
+ _serverProcess.ErrorDataReceived += (s, e) => { if (!string.IsNullOrEmpty(e.Data)) Log("[srv-err] " + e.Data); };
149
+ _serverProcess.Start();
150
+ _serverProcess.BeginOutputReadLine();
151
+ _serverProcess.BeginErrorReadLine();
152
+ Log("Server started PID=" + _serverProcess.Id);
153
+ }
154
+ catch (Exception ex)
155
+ {
156
+ Log("StartServer FAILED: " + ex.Message);
157
+ }
158
+ }
159
+
160
+ // ─── 健康检查 ────────────────────────────────
161
+ private static bool IsOnline()
162
+ {
163
+ try
164
+ {
165
+ var req = WebRequest.CreateHttp("http://127.0.0.1:50816/health");
166
+ req.Timeout = 1500;
167
+ using (var resp = req.GetResponse()) { return true; }
168
+ }
169
+ catch { return false; }
170
+ }
171
+
172
+ private static void HealthCheck(object state)
173
+ {
174
+ try
175
+ {
176
+ if (!IsOnline())
177
+ {
178
+ Log("Health check FAILED, restarting...");
179
+ KillOldServers();
180
+ StartServer();
181
+ Thread.Sleep(3000);
182
+ if (IsOnline())
183
+ _notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已自动重启 ✓", ToolTipIcon.Info);
184
+ else
185
+ _notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器重启失败 ⚠", ToolTipIcon.Error);
186
+ }
187
+ }
188
+ catch (Exception ex) { Log("HealthCheck error: " + ex.Message); }
189
+ }
190
+
191
+ // ─── 创建托盘图标 ────────────────────────────
192
+ private static void CreateTrayIcon()
193
+ {
194
+ var bmp = new Bitmap(16, 16);
195
+ using (var g = Graphics.FromImage(bmp))
196
+ {
197
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
198
+ g.Clear(Color.Transparent);
199
+ using (var brush = new SolidBrush(Color.FromArgb(0, 120, 215)))
200
+ g.FillEllipse(brush, 0, 0, 15, 15);
201
+ using (var font = new Font("Segoe UI", 8.5f, FontStyle.Bold))
202
+ using (var fg = new SolidBrush(Color.White))
203
+ g.DrawString("P", font, fg, 3, 1.5f);
204
+ }
205
+ var icon = Icon.FromHandle(bmp.GetHicon());
206
+
207
+ _notifyIcon = new NotifyIcon();
208
+ _notifyIcon.Icon = icon;
209
+ _notifyIcon.Text = "Pan Router\n端口 50816 | 运行中";
210
+ _notifyIcon.Visible = true;
211
+ Log("Tray icon created");
212
+
213
+ // Left click: balloon status
214
+ _notifyIcon.MouseClick += (s, e) =>
215
+ {
216
+ if (e.Button == MouseButtons.Left)
217
+ {
218
+ bool online = IsOnline();
219
+ _notifyIcon.ShowBalloonTip(2000, "Pan Router",
220
+ online ? "运行正常 ✓ (端口 50816)" : "服务器未响应 ⚠",
221
+ online ? ToolTipIcon.Info : ToolTipIcon.Error);
222
+ }
223
+ };
224
+
225
+ // Right-click menu
226
+ var menu = new ContextMenuStrip();
227
+
228
+ var titleItem = new ToolStripMenuItem("Pan Router - :50816") { Enabled = false };
229
+ titleItem.Font = new Font("Segoe UI", 9, FontStyle.Bold);
230
+ menu.Items.Add(titleItem);
231
+ menu.Items.Add(new ToolStripSeparator());
232
+
233
+ // Autostart
234
+ var autoItem = new ToolStripMenuItem("开机自启动");
235
+ autoItem.Checked = IsAutostart();
236
+ autoItem.Click += (s, e) =>
237
+ {
238
+ if (autoItem.Checked)
239
+ {
240
+ SetAutostart(false);
241
+ autoItem.Checked = false;
242
+ _notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", ToolTipIcon.Info);
243
+ Log("Autostart OFF");
244
+ }
245
+ else
246
+ {
247
+ SetAutostart(true);
248
+ autoItem.Checked = true;
249
+ _notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", ToolTipIcon.Info);
250
+ Log("Autostart ON");
251
+ }
252
+ };
253
+ menu.Items.Add(autoItem);
254
+ menu.Items.Add(new ToolStripSeparator());
255
+
256
+ // Exit
257
+ var exitItem = new ToolStripMenuItem("退出");
258
+ exitItem.Click += (s, e) =>
259
+ {
260
+ Log("Exit clicked");
261
+ Cleanup();
262
+ Application.Exit();
263
+ };
264
+ menu.Items.Add(exitItem);
265
+
266
+ _notifyIcon.ContextMenuStrip = menu;
267
+
268
+ // Cleanup on exit
269
+ Application.ApplicationExit += (s, e) => { Cleanup(); bmp.Dispose(); };
270
+ }
271
+
272
+ // ─── 开机自启动 ──────────────────────────────
273
+ private static bool IsAutostart()
274
+ {
275
+ try
276
+ {
277
+ using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run"))
278
+ return key?.GetValue("PanRouter") != null;
279
+ }
280
+ catch { return false; }
281
+ }
282
+
283
+ private static void SetAutostart(bool enable)
284
+ {
285
+ try
286
+ {
287
+ using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true))
288
+ {
289
+ if (enable)
290
+ {
291
+ var exePath = Assembly.GetExecutingAssembly().Location;
292
+ key.SetValue("PanRouter",
293
+ $"\"{exePath}\" --serverPath:\"{_serverPath}\"");
294
+ }
295
+ else
296
+ {
297
+ key.DeleteValue("PanRouter", false);
298
+ }
299
+ }
300
+ }
301
+ catch (Exception ex) { Log("Autostart error: " + ex.Message); }
302
+ }
303
+
304
+ // ─── 退出清理 ────────────────────────────────
305
+ private static void Cleanup()
306
+ {
307
+ try
308
+ {
309
+ Log("Cleanup starting...");
310
+ _healthTimer?.Dispose();
311
+ if (_serverProcess != null && !_serverProcess.HasExited)
312
+ {
313
+ Log("Killing server PID=" + _serverProcess.Id);
314
+ _serverProcess.Kill();
315
+ _serverProcess.WaitForExit(3000);
316
+ _serverProcess.Dispose();
317
+ Log("Server killed");
318
+ }
319
+ KillOldServers();
320
+ try { _log?.Close(); } catch { }
321
+ }
322
+ catch (Exception ex) { try { _log?.WriteLine("Cleanup error: " + ex.Message); } catch { } }
323
+ }
324
+ }
package/tray-manager.ps1 CHANGED
@@ -5,168 +5,120 @@
5
5
  在通知区域(右下角)显示图标,隐藏命令窗口。
6
6
  右键菜单: 状态, 开机自启动开关, 退出
7
7
 
8
- 用法:
9
- powershell -STA -File tray-manager.ps1 -ServerPath "server.mjs"
8
+ 用法(必须 STA):
9
+ powershell -ExecutionPolicy Bypass -STA -WindowStyle Hidden -File tray-manager.ps1 -ServerPath "server.mjs"
10
+
11
+ 注意: 不能在脚本中弹任何对话框(隐藏窗口下没人点)
10
12
  #>
11
13
 
12
- param(
13
- [string]$ServerPath
14
- )
15
-
16
- # ─── STA 检查 ──────────────────────────────────
17
- if ([System.Threading.Thread]::CurrentThread.GetApartmentState() -ne "STA") {
18
- $r = [System.Windows.Forms.MessageBox]::Show(
19
- "Pan Router 需要 STA 模式运行。`n是否自动以 STA 模式重新启动?",
20
- "Pan Router",
21
- "YesNo",
22
- "Warning"
23
- )
24
- if ($r -eq "Yes") {
25
- powershell -STA -File $MyInvocation.MyCommand.Path -ServerPath $ServerPath
26
- }
27
- exit 1
28
- }
14
+ param([string]$ServerPath)
15
+
16
+ # ─── 启动即写日志 ──────────────────────────────
17
+ $logPath = "$env:TEMP\panrouter-tray.log"
18
+ "===" + (Get-Date -Format "HH:mm:ss") + " PanRouter Tray===" | Out-File $logPath
19
+ function Log { param($m) (Get-Date -Format "HH:mm:ss") + " " + $m | Out-File -Append $logPath }
29
20
 
21
+ Log "ServerPath=$ServerPath"
22
+ Log "Args=$([environment]::CommandLine)"
23
+
24
+ # ─── 加载 WinForms ─────────────────────────────
30
25
  Add-Type -AssemblyName System.Windows.Forms
31
26
  Add-Type -AssemblyName System.Drawing
27
+ Log "Assemblies loaded"
32
28
 
33
29
  # ─── 常量 ──────────────────────────────────────
34
- $scriptPath = $MyInvocation.MyCommand.Path
35
30
  $autostartName = "PanRouter"
36
31
  $runKeyPath = "Software\Microsoft\Windows\CurrentVersion\Run"
37
-
38
- # 取 npm 全局安装目录作为图标标题提示
39
- $pkgDir = Split-Path (Split-Path $ServerPath -Parent) -Parent
40
- $pkgName = Split-Path $pkgDir -Leaf
32
+ $appDir = Split-Path $ServerPath -Parent
41
33
  $tooltipText = "Pan Router`n端口 50816 | 运行中"
42
34
 
43
- # ─── 图标生成 ──────────────────────────────────
44
- function New-TrayIcon {
45
- $bmp = New-Object System.Drawing.Bitmap(16, 16)
46
- $g = [System.Drawing.Graphics]::FromImage($bmp)
47
- $g.SmoothingMode = 'HighQuality'
48
- $g.Clear([System.Drawing.Color]::Transparent)
49
-
50
- # 蓝色圆底
51
- $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
52
- $g.FillEllipse($brush, 0, 0, 15, 15)
53
-
54
- # 白色 "P" 字
55
- $font = New-Object System.Drawing.Font("Segoe UI", 8.5, [System.Drawing.FontStyle]::Bold)
56
- $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
57
- $g.DrawString("P", $font, $fg, 3, 1.5)
58
- $font.Dispose()
59
- $fg.Dispose()
60
- $brush.Dispose()
61
- $g.Dispose()
62
-
63
- $hIcon = $bmp.GetHicon()
64
- $icon = [System.Drawing.Icon]::FromHandle($hIcon)
65
- $bmp.Dispose()
66
- return $icon, $hIcon
67
- }
68
-
69
- # ─── 启动隐藏的 Node 服务器 ─────────────────────
70
- function Start-ServerHidden {
71
- param([string]$ServerPath)
35
+ # ─── 生成图标 ──────────────────────────────────
36
+ $bmp = New-Object System.Drawing.Bitmap(16, 16)
37
+ $g = [System.Drawing.Graphics]::FromImage($bmp)
38
+ $g.SmoothingMode = 'HighQuality'
39
+ $g.Clear([System.Drawing.Color]::Transparent)
40
+ $brush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(0, 120, 215))
41
+ $g.FillEllipse($brush, 2, 2, 12, 12)
42
+ $font = New-Object System.Drawing.Font("Segoe UI", 8, [System.Drawing.FontStyle]::Bold)
43
+ $fg = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
44
+ $g.DrawString("P", $font, $fg, 4, 2.5)
45
+ $font.Dispose(); $fg.Dispose(); $brush.Dispose(); $g.Dispose()
46
+ $hIcon = $bmp.GetHicon()
47
+ $icon = [System.Drawing.Icon]::FromHandle($hIcon)
48
+ $bmp.Dispose()
49
+ Log "Icon generated"
50
+
51
+ # ─── NotifyIcon ────────────────────────────────
52
+ $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
53
+ $notifyIcon.Icon = $icon
54
+ $notifyIcon.Text = $tooltipText
55
+ $notifyIcon.Visible = $true
56
+ Log "NotifyIcon visible"
72
57
 
73
- $serverDir = Split-Path $ServerPath -Parent
58
+ # ─── 启动隐藏服务器 ────────────────────────────
59
+ function Start-Server {
74
60
  $psi = New-Object System.Diagnostics.ProcessStartInfo
75
61
  $psi.FileName = "node"
76
62
  $psi.Arguments = "`"$ServerPath`""
77
- $psi.WorkingDirectory = $serverDir
63
+ $psi.WorkingDirectory = $appDir
78
64
  $psi.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
79
65
  $psi.CreateNoWindow = $true
80
- $psi.UseShellExecute = $false
81
- $psi.RedirectStandardOutput = $true
82
- $psi.RedirectStandardError = $true
83
-
66
+ $psi.UseShellExecute = $true # 关键: 不重定向输出, 无缓冲死锁
84
67
  try {
85
- $proc = [System.Diagnostics.Process]::Start($psi)
86
- return $proc
68
+ $p = [System.Diagnostics.Process]::Start($psi)
69
+ Log "Server started PID=$($p.Id)"
70
+ return $p
87
71
  } catch {
72
+ Log "Server start FAILED: $_"
88
73
  return $null
89
74
  }
90
75
  }
91
76
 
92
- # ─── 检查服务器是否在线 ─────────────────────────
93
- function Test-ServerOnline {
77
+ # ─── 杀掉旧服务器 (wmic, 不依赖 CIM/WMI 版本) ─
78
+ function Stop-OldServer {
79
+ try {
80
+ $wmicOut = & wmic process where "name='node.exe'" get ProcessId,CommandLine /format:csv 2>$null
81
+ foreach ($line in $wmicOut) {
82
+ if ($line -match '(\d+),.*?server\.mjs') {
83
+ $pid = $Matches[1]
84
+ Log "Kill old PID=$pid"
85
+ Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
86
+ }
87
+ }
88
+ } catch { Log "KillOld: $_" }
89
+ }
90
+
91
+ # ─── 健康检查 ──────────────────────────────────
92
+ function Test-Online {
94
93
  try {
95
- $req = [System.Net.HttpWebRequest]::Create("http://127.0.0.1:50816/health")
96
- $req.Method = "GET"
94
+ $req = [System.Net.WebRequest]::Create("http://127.0.0.1:50816/health")
97
95
  $req.Timeout = 1500
98
96
  $resp = $req.GetResponse()
99
97
  $resp.Close()
100
98
  return $true
101
- } catch {
102
- return $false
103
- }
104
- }
105
-
106
- # ─── 杀掉旧 Pan Router 进程 (WMI 方式, 兼容 PS5.1) ──────────
107
- function Stop-OldServer {
108
- try {
109
- $existing = Get-CimInstance -ClassName Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
110
- Where-Object { $_.CommandLine -match "server\.mjs" }
111
- foreach ($p in $existing) {
112
- $pid = $p.ProcessId
113
- Stop-Process -Id $pid -Force -ErrorAction SilentlyContinue
114
- }
115
- } catch {}
99
+ } catch { return $false }
116
100
  }
117
101
 
118
- # ═══════════════════════════════════════════════
119
- # 主流程
120
- # ═══════════════════════════════════════════════
102
+ # ═══════════════ 主流程 ═══════════════
121
103
 
122
- # 1. 杀掉旧服务器
104
+ # 1. 杀旧 + 启动
123
105
  Stop-OldServer
106
+ $serverProcess = Start-Server
124
107
 
125
- # 2. 生成图标
126
- $icon, $hIcon = New-TrayIcon
127
-
128
- # 3. 创建 NotifyIcon
129
- $notifyIcon = New-Object System.Windows.Forms.NotifyIcon
130
- $notifyIcon.Icon = $icon
131
- $notifyIcon.Text = $tooltipText
132
- $notifyIcon.Visible = $true
133
-
134
- # 4. 启动隐藏服务器进程
135
- $serverProcess = Start-ServerHidden -ServerPath $ServerPath
136
-
137
- # 5. 等待服务器就绪(后台检查,不阻塞 UI)
138
- $checkTimer = New-Object System.Windows.Forms.Timer
139
- $checkTimer.Interval = 500
140
- $checkCount = 0
141
- $checkTimer.Add_Tick({
142
- $checkCount++
143
- if (Test-ServerOnline) {
144
- $checkTimer.Stop()
145
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", "Info")
146
- } elseif ($checkCount -ge 10) {
147
- $checkTimer.Stop()
148
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器启动可能较慢,尝试访问中...", "Warning")
149
- }
150
- })
151
- $checkTimer.Start()
152
-
153
- # 6. 左键点击: 弹气泡提示状态
154
- $notifyIcon.Add_MouseClick({
155
- param($sender, $e)
156
- if ($e.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
157
- $online = Test-ServerOnline
158
- if ($online) {
159
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "运行正常 ✓ (端口 50816)", "Info")
160
- } else {
161
- $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器未响应 ⚠", "Error")
162
- }
163
- }
164
- })
108
+ # 2. 前台等待就绪 (10秒超时)
109
+ $ready = $false
110
+ for ($i = 0; $i -lt 20; $i++) {
111
+ Start-Sleep -Milliseconds 500
112
+ if (Test-Online) { $ready = $true; break }
113
+ }
114
+ Log "Server ready=$ready"
115
+ if ($ready) {
116
+ $notifyIcon.ShowBalloonTip(3000, "Pan Router", "服务器已就绪 (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
117
+ }
165
118
 
166
- # 7. 右键菜单
119
+ # 3. 右键菜单
167
120
  $menu = New-Object System.Windows.Forms.ContextMenuStrip
168
121
 
169
- # 标题行
170
122
  $titleItem = New-Object System.Windows.Forms.ToolStripMenuItem
171
123
  $titleItem.Text = "Pan Router - :50816"
172
124
  $titleItem.Enabled = $false
@@ -174,34 +126,34 @@ $titleItem.Font = New-Object System.Drawing.Font("Segoe UI", 9, [System.Drawing.
174
126
  $menu.Items.Add($titleItem)
175
127
  $menu.Items.Add("-")
176
128
 
177
- # 开机自启动
178
129
  $autoStartItem = New-Object System.Windows.Forms.ToolStripMenuItem
179
130
  $autoStartItem.Text = "开机自启动"
180
- $runKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
181
- $autoStartItem.Checked = ($runKey.GetValue($autostartName) -ne $null)
182
- $runKey.Close()
131
+ $rk = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
132
+ $autoStartItem.Checked = ($rk.GetValue($autostartName) -ne $null)
133
+ $rk.Close()
183
134
  $autoStartItem.Add_Click({
184
- $rk = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
135
+ $rk2 = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($runKeyPath, $true)
185
136
  if ($autoStartItem.Checked) {
186
- $rk.DeleteValue($autostartName, $false)
137
+ $rk2.DeleteValue($autostartName, $false)
187
138
  $autoStartItem.Checked = $false
188
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", "Info")
139
+ $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已关闭", [System.Windows.Forms.ToolTipIcon]::Info)
140
+ Log "Autostart OFF"
189
141
  } else {
190
- $cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$scriptPath`" -ServerPath `"$ServerPath`""
191
- $rk.SetValue($autostartName, $cmd)
142
+ $cmd = "powershell -ExecutionPolicy Bypass -WindowStyle Hidden -STA -File `"$($MyInvocation.MyCommand.Path)`" -ServerPath `"$ServerPath`""
143
+ $rk2.SetValue($autostartName, $cmd)
192
144
  $autoStartItem.Checked = $true
193
- $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", "Info")
145
+ $notifyIcon.ShowBalloonTip(2000, "Pan Router", "开机自启动已开启 ✓", [System.Windows.Forms.ToolTipIcon]::Info)
146
+ Log "Autostart ON"
194
147
  }
195
- $rk.Close()
148
+ $rk2.Close()
196
149
  })
197
150
  $menu.Items.Add($autoStartItem)
198
-
199
151
  $menu.Items.Add("-")
200
152
 
201
- # 退出
202
153
  $exitItem = New-Object System.Windows.Forms.ToolStripMenuItem
203
154
  $exitItem.Text = "退出"
204
155
  $exitItem.Add_Click({
156
+ Log "Exit clicked"
205
157
  $notifyIcon.Visible = $false
206
158
  [System.Windows.Forms.Application]::Exit()
207
159
  })
@@ -209,29 +161,38 @@ $menu.Items.Add($exitItem)
209
161
 
210
162
  $notifyIcon.ContextMenuStrip = $menu
211
163
 
212
- # 8. 退出清理
213
- $cleanup = {
214
- try {
215
- if ($checkTimer) { $checkTimer.Stop(); $checkTimer.Dispose() }
216
- if ($serverProcess -and !$serverProcess.HasExited) {
217
- $serverProcess.Kill()
218
- $serverProcess.WaitForExit(3000)
219
- $serverProcess.Dispose()
164
+ # 4. 左键: 状态
165
+ $notifyIcon.Add_MouseClick({
166
+ if ($_.Button -eq [System.Windows.Forms.MouseButtons]::Left) {
167
+ if (Test-Online) {
168
+ $notifyIcon.ShowBalloonTip(2000, "Pan Router", "运行正常 ✓ (端口 50816)", [System.Windows.Forms.ToolTipIcon]::Info)
169
+ } else {
170
+ $notifyIcon.ShowBalloonTip(2000, "Pan Router", "服务器未响应 ⚠", [System.Windows.Forms.ToolTipIcon]::Error)
220
171
  }
221
- # 再补一刀: 确保无残留
222
- Stop-OldServer
223
- } catch {}
224
- }
172
+ }
173
+ })
225
174
 
175
+ # 5. 退出清理
226
176
  [System.Windows.Forms.Application]::ApplicationExit += {
227
- & $cleanup
177
+ Log "AppExit cleanup"
178
+ if ($serverProcess -and !$serverProcess.HasExited) {
179
+ $serverProcess.Kill()
180
+ $serverProcess.WaitForExit(3000)
181
+ Log "Server killed"
182
+ }
183
+ Stop-OldServer
228
184
  $notifyIcon.Dispose()
229
185
  [System.Runtime.InteropServices.Marshal]::DestroyIcon($hIcon)
230
186
  $icon.Dispose()
231
187
  }
232
188
 
233
- # 9. 运行消息循环
189
+ # 6. 运行消息循环
190
+ Log "Entering message loop"
234
191
  [System.Windows.Forms.Application]::Run()
192
+ Log "Message loop exited"
235
193
 
236
- # 10. 循环结束后再次清理
237
- & $cleanup
194
+ # 7. 循环结束后再清理一次
195
+ try {
196
+ if ($serverProcess -and !$serverProcess.HasExited) { $serverProcess.Kill() }
197
+ Stop-OldServer
198
+ } catch {}