Repository navigation
Expand file tree
/
Copy pathWinGui.cs
More file actions
432 lines (404 loc) · 38.6 KB
/
Copy pathWinGui.cs
File metadata and controls
432 lines (404 loc) · 38.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace NetPulse
{
internal static class GuiProgram
{
[STAThread] private static void Main()
{
bool created;
using (Mutex mutex = new Mutex(true, "Local\\NetPulse.Gui.SingleInstance", out created))
{
if (!created) { MessageBox.Show("NetPulse is already running. / NetPulse 已經在執行中。", "NetPulse"); return; }
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
internal sealed class MainForm : Form
{
private const int HotkeyId = 0x4E50, WmHotkey = 0x0312, GwlExStyle = -20;
private const uint ModAlt = 1, ModControl = 2, ModShift = 4, ModWin = 8, ModNoRepeat = 0x4000, GwOwner = 4;
private const long WsExToolWindow = 0x80, WsExAppWindow = 0x40000;
[DllImport("user32.dll", SetLastError = true)] private static extern bool RegisterHotKey(IntPtr h, int id, uint mods, uint key);
[DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr h, int id);
[DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr h);
[DllImport("user32.dll")] private static extern IntPtr GetWindow(IntPtr h, uint command);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW")] private static extern IntPtr GetWindowLongPtr(IntPtr h, int index);
[DllImport("user32.dll")] private static extern short GetAsyncKeyState(int key);
private readonly string _settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "NetPulse.gui.config.txt");
private readonly string _logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs");
private bool _english, _running, _registered, _closeAfterStop, _hotkeyCapturePending;
private DateTime _sessionStarted, _lastLocalHotkey = DateTime.MinValue;
private int _markerCount;
private string _configuredProcess = "";
private Keys _hotkeyKey = Keys.F8;
private bool _hotkeyCtrl = true, _hotkeyAlt = true, _hotkeyShift, _hotkeyWin;
private SessionData _analysisData;
private MenuStrip _menu;
private ToolStripMenuItem _monitorMenu, _analysisMenu, _languageMenu, _zhMenu, _enMenu;
private Panel _monitorPage, _analysisPage;
private Label _processCaption, _stateCaption, _elapsedCaption, _modeCaption, _cpuCaption, _targetCaption,
_networkCaption, _trafficCaption, _markerCaption, _lastCaption, _state, _elapsed, _mode, _cpu, _target,
_network, _traffic, _markers, _hotkeyStatus, _lastLog, _analysisPathCaption, _analysisPath, _captureHint;
private GroupBox _targetGroup, _monitorGroup, _hotkeyGroup;
private ComboBox _processCombo;
private TextBox _hotkeyBox;
private Button _refreshButton, _startButton, _stopButton, _markButton, _logsButton, _applyButton,
_latestButton, _browseButton, _openSessionButton;
private RadioButton _global, _local;
private Chart _chart;
private SplitContainer _analysisSplit;
private RichTextBox _analysisText;
private System.Windows.Forms.Timer _timer;
public MainForm()
{
ClientSize = new Size(1040, 720); MinimumSize = new Size(900, 650); StartPosition = FormStartPosition.CenterScreen;
BackColor = Color.FromArgb(244, 247, 250); ForeColor = Color.FromArgb(31, 41, 55);
Font = new Font("Microsoft JhengHei UI", 9F); KeyPreview = true;
try { Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath); } catch { }
BuildInterface(); LoadSettings(); ApplyLanguage();
_timer = new System.Windows.Forms.Timer { Interval = 250 };
_timer.Tick += delegate { if (_running) _elapsed.Text = Duration(DateTime.Now - _sessionStarted); };
_timer.Start(); Shown += delegate { ApplyHotkey(false); }; FormClosing += OnClosing;
}
private void BuildInterface()
{
_menu = new MenuStrip { BackColor = Color.White, Font = new Font(Font.FontFamily, 9.5F) };
_monitorMenu = new ToolStripMenuItem(); _analysisMenu = new ToolStripMenuItem(); _languageMenu = new ToolStripMenuItem();
_zhMenu = new ToolStripMenuItem("繁體中文"); _enMenu = new ToolStripMenuItem("English");
_languageMenu.DropDownItems.AddRange(new ToolStripItem[] { _zhMenu, _enMenu });
_menu.Items.AddRange(new ToolStripItem[] { _monitorMenu, _analysisMenu, _languageMenu });
_monitorMenu.Click += delegate { ShowPage(false); }; _analysisMenu.Click += delegate { ShowPage(true); };
_zhMenu.Click += delegate { SetLanguage(false); }; _enMenu.Click += delegate { SetLanguage(true); };
Controls.Add(_menu); MainMenuStrip = _menu;
BuildMonitorPage(); BuildAnalysisPage(); ShowPage(false);
}
private void BuildMonitorPage()
{
_monitorPage = new Panel { Dock = DockStyle.Fill, AutoScroll = true }; Controls.Add(_monitorPage);
_targetGroup = Group(_monitorPage, 20, 16, 1000, 78); _targetGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
_processCaption = Caption(_targetGroup, 18, 32);
_processCombo = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, DisplayMember = "DisplayText", ValueMember = "ProcessName", Location = new Point(118, 27), Size = new Size(680, 26), Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right, BackColor = Color.White };
_targetGroup.Controls.Add(_processCombo);
_refreshButton = Button(818, 24, 150, 32, Color.FromArgb(67, 83, 104)); _refreshButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
_refreshButton.Click += delegate { RefreshProcesses(SelectedProcess); }; _targetGroup.Controls.Add(_refreshButton);
_monitorGroup = Group(_monitorPage, 20, 106, 1000, 282); _monitorGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
_stateCaption = Caption(_monitorGroup, 18, 31); _state = Value(_monitorGroup, "--", 118, 29, 250); _state.Font = new Font(Font.FontFamily, 11F, FontStyle.Bold);
_elapsedCaption = Caption(_monitorGroup, 520, 31); _elapsed = Value(_monitorGroup, "00:00:00", 635, 29, 220);
_modeCaption = Caption(_monitorGroup, 18, 68); _mode = Value(_monitorGroup, "--", 118, 66, 800);
_cpuCaption = Caption(_monitorGroup, 18, 105); _cpu = Value(_monitorGroup, "--", 118, 103, 800);
_targetCaption = Caption(_monitorGroup, 18, 142); _target = Value(_monitorGroup, "--", 118, 140, 800);
_networkCaption = Caption(_monitorGroup, 18, 179); _network = Value(_monitorGroup, "--", 118, 177, 800);
_trafficCaption = Caption(_monitorGroup, 18, 216); _traffic = Value(_monitorGroup, "--", 118, 214, 340);
_markerCaption = Caption(_monitorGroup, 520, 216); _markers = Value(_monitorGroup, "0", 635, 214, 320);
_startButton = Button(18, 244, 135, 31, Color.FromArgb(29, 112, 196));
_stopButton = Button(163, 244, 95, 31, Color.FromArgb(181, 58, 58)); _stopButton.Enabled = false;
_markButton = Button(268, 244, 125, 31, Color.FromArgb(201, 105, 20)); _markButton.Enabled = false;
_logsButton = Button(403, 244, 170, 31, Color.FromArgb(67, 83, 104));
_startButton.Click += delegate { StartMonitoring(); }; _stopButton.Click += delegate { StopMonitoring(); };
_markButton.Click += delegate { AddMarker(_english ? "button" : "按鈕"); }; _logsButton.Click += delegate { OpenFolder(_logDirectory); };
_monitorGroup.Controls.AddRange(new Control[] { _startButton, _stopButton, _markButton, _logsButton });
_hotkeyGroup = Group(_monitorPage, 20, 400, 1000, 156); _hotkeyGroup.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
Label keyCaption = Caption(_hotkeyGroup, 18, 33); keyCaption.Name = "KeyCaption";
_hotkeyBox = new TextBox { Location = new Point(118, 27), Size = new Size(220, 27), BackColor = Color.White, ReadOnly = true, ShortcutsEnabled = false, TextAlign = HorizontalAlignment.Center, Font = new Font(Font.FontFamily, 9.5F, FontStyle.Bold) };
_hotkeyBox.KeyDown += CaptureHotkey;
_hotkeyBox.Enter += delegate { _hotkeyCapturePending = false; if (_registered) { UnregisterHotKey(Handle, HotkeyId); _registered = false; } _hotkeyBox.BackColor = Color.FromArgb(232, 248, 252); _hotkeyBox.SelectAll(); };
_hotkeyBox.Leave += delegate { _hotkeyBox.BackColor = Color.White; if (!_registered && !_hotkeyCapturePending) ApplyHotkey(false); };
_captureHint = new Label { Location = new Point(358, 31), Size = new Size(410, 24), ForeColor = Color.FromArgb(85, 100, 116) };
_hotkeyGroup.Controls.Add(_hotkeyBox); _hotkeyGroup.Controls.Add(_captureHint);
_applyButton = Button(795, 24, 173, 32, Color.FromArgb(29, 132, 90)); _applyButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;
_applyButton.Click += delegate { ApplyHotkey(true); }; _hotkeyGroup.Controls.Add(_applyButton);
_global = new RadioButton { Location = new Point(20, 68), Size = new Size(740, 24) };
_local = new RadioButton { Location = new Point(20, 94), Size = new Size(740, 24) };
_hotkeyStatus = new Label { Location = new Point(20, 123), Size = new Size(940, 23), ForeColor = Color.FromArgb(70, 90, 108) };
_hotkeyGroup.Controls.AddRange(new Control[] { _global, _local, _hotkeyStatus });
_lastCaption = new Label { Location = new Point(24, 575), AutoSize = true, ForeColor = Color.FromArgb(85, 100, 116) };
_lastLog = new Label { Location = new Point(128, 575), Size = new Size(860, 46), ForeColor = Color.FromArgb(52, 70, 89), AutoEllipsis = true, Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right };
_monitorPage.Controls.Add(_lastCaption); _monitorPage.Controls.Add(_lastLog);
}
private void BuildAnalysisPage()
{
_analysisPage = new Panel { Dock = DockStyle.Fill, Visible = false }; Controls.Add(_analysisPage);
_latestButton = Button(20, 14, 145, 34, Color.FromArgb(29, 112, 196));
_browseButton = Button(175, 14, 180, 34, Color.FromArgb(67, 83, 104));
_openSessionButton = Button(365, 14, 185, 34, Color.FromArgb(29, 132, 90)); _openSessionButton.Enabled = false;
_latestButton.Click += delegate { LoadLatest(); }; _browseButton.Click += delegate { BrowseFolder(); };
_openSessionButton.Click += delegate { if (_analysisData != null) OpenFolder(_analysisData.SessionFolder); };
_analysisPage.Controls.AddRange(new Control[] { _latestButton, _browseButton, _openSessionButton });
_analysisPathCaption = new Label { Location = new Point(22, 62), Size = new Size(72, 24), ForeColor = Color.FromArgb(85, 100, 116) };
_analysisPath = new Label { Location = new Point(94, 60), Size = new Size(900, 28), AutoEllipsis = true, ForeColor = Color.FromArgb(52, 70, 89) };
_analysisPage.Controls.Add(_analysisPathCaption); _analysisPage.Controls.Add(_analysisPath);
_analysisSplit = new SplitContainer { Location = new Point(20, 94), Size = new Size(1000, 588), Orientation = Orientation.Horizontal, SplitterDistance = 365, SplitterWidth = 6, BackColor = Color.FromArgb(220, 226, 233) };
_chart = new Chart { Dock = DockStyle.Fill, BackColor = Color.White };
_analysisText = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, BackColor = Color.White, BorderStyle = BorderStyle.None, Font = new Font("Consolas", 9.5F), ForeColor = Color.FromArgb(32, 47, 62), DetectUrls = false };
_analysisSplit.Panel1.Controls.Add(_chart); _analysisSplit.Panel2.Controls.Add(_analysisText); _analysisPage.Controls.Add(_analysisSplit);
_analysisPage.Resize += delegate { LayoutAnalysisPage(); };
LayoutAnalysisPage();
}
private void LayoutAnalysisPage()
{
if (_analysisPage == null || _analysisSplit == null) return;
int width = Math.Max(320, _analysisPage.ClientSize.Width - 40);
int height = Math.Max(220, _analysisPage.ClientSize.Height - 112);
_analysisPath.Size = new Size(Math.Max(180, _analysisPage.ClientSize.Width - 116), 28);
_analysisSplit.SetBounds(20, 94, width, height);
bool stacked = width < 1180;
Orientation orientation = stacked ? Orientation.Horizontal : Orientation.Vertical;
if (_analysisSplit.Orientation != orientation) _analysisSplit.Orientation = orientation;
int total = stacked ? height : width;
int minimumFirst = stacked ? 250 : 520;
int minimumSecond = stacked ? 130 : 260;
int desired = stacked ? (int)(total * 0.64) : (int)(total * 0.68);
int maximum = total - _analysisSplit.SplitterWidth - minimumSecond;
if (maximum < 25) maximum = Math.Max(25, total / 2);
desired = Math.Max(Math.Min(minimumFirst, maximum), Math.Min(desired, maximum));
if (desired > 0 && desired < total - _analysisSplit.SplitterWidth) _analysisSplit.SplitterDistance = desired;
}
private GroupBox Group(Control parent, int x, int y, int w, int h) { GroupBox g = new GroupBox { Location = new Point(x, y), Size = new Size(w, h), BackColor = Color.White, ForeColor = Color.FromArgb(40, 55, 72) }; parent.Controls.Add(g); return g; }
private static Label Caption(Control parent, int x, int y) { Label l = new Label { Location = new Point(x, y), Size = new Size(96, 24), ForeColor = Color.FromArgb(68, 82, 97) }; parent.Controls.Add(l); return l; }
private static Label Value(Control parent, string text, int x, int y, int w) { Label l = new Label { Text = text, Location = new Point(x, y), Size = new Size(w, 27), ForeColor = Color.FromArgb(31, 41, 55) }; parent.Controls.Add(l); return l; }
private Button Button(int x, int y, int w, int h, Color color)
{
Button b = new Button { Location = new Point(x, y), Size = new Size(w, h), FlatStyle = FlatStyle.Flat, BackColor = color, ForeColor = Color.White, Font = new Font(Font.FontFamily, 9F, FontStyle.Bold), UseVisualStyleBackColor = false };
b.FlatAppearance.BorderSize = 0; b.FlatAppearance.MouseOverBackColor = ControlPaint.Light(color, .08F); b.FlatAppearance.MouseDownBackColor = ControlPaint.Dark(color, .08F);
b.EnabledChanged += delegate { b.BackColor = b.Enabled ? color : Color.FromArgb(207, 214, 222); b.ForeColor = b.Enabled ? Color.White : Color.FromArgb(105, 116, 129); }; return b;
}
private void SetLanguage(bool english) { string selectedProcess = SelectedProcess; _english = english; ApplyLanguage(); RefreshProcesses(selectedProcess); ApplyHotkey(false); try { SaveSettings(); } catch { } if (_analysisData != null) RenderAnalysis(); }
private void ApplyLanguage()
{
Text = _english ? "NetPulse - Network & CPU Monitor" : "NetPulse - 網路與 CPU 監測";
_monitorMenu.Text = _english ? "Monitoring" : "即時監測"; _analysisMenu.Text = _english ? "Data Analysis" : "數據分析"; _languageMenu.Text = _english ? "Language" : "語言";
_zhMenu.Checked = !_english; _enMenu.Checked = _english;
_targetGroup.Text = _english ? "Target application (optional; open taskbar windows only)" : "監測目標(選填;只列出工作列中已開啟視窗)";
_processCaption.Text = _english ? "Application:" : "執行程式:"; _refreshButton.Text = _english ? "Refresh" : "重新整理";
_monitorGroup.Text = _english ? "Live Monitoring" : "即時監測";
_stateCaption.Text = _english ? "Status:" : "狀態:"; _elapsedCaption.Text = _english ? "Elapsed:" : "經過時間:"; _modeCaption.Text = _english ? "Mode:" : "模式:";
_cpuCaption.Text = "CPU:"; _targetCaption.Text = _english ? "Target:" : "目標程式:"; _networkCaption.Text = _english ? "Network:" : "網路:";
_trafficCaption.Text = _english ? "Traffic:" : "流量:"; _markerCaption.Text = _english ? "Markers:" : "標記:";
_startButton.Text = _english ? "Start" : "開始監測"; _stopButton.Text = _english ? "Stop" : "停止"; _markButton.Text = _english ? "Add Marker" : "立即標記"; _logsButton.Text = _english ? "Open Output Folder" : "開啟紀錄資料夾";
_hotkeyGroup.Text = _english ? "Marker Hotkey" : "標記快捷鍵"; Control[] key = _hotkeyGroup.Controls.Find("KeyCaption", false); if (key.Length > 0) key[0].Text = _english ? "Shortcut:" : "快捷鍵:";
_captureHint.Text = _english ? "Click the field, press a shortcut, then Apply & Save" : "點選欄位並按下快捷鍵,再按「套用並儲存」";
_hotkeyBox.Text = HotkeyDisplay();
_applyButton.Text = _english ? "Apply & Save" : "套用並儲存";
_global.Text = _english ? "Global: detect the hotkey in games and other windows" : "全域偵測:在遊戲或其他視窗中也能按下標記";
_local.Text = _english ? "NetPulse only: detect the hotkey only while this window is active" : "僅本工具視窗:必須切回 NetPulse 才會偵測";
_lastCaption.Text = _english ? "Latest output:" : "最近紀錄:"; if (!_running && string.IsNullOrWhiteSpace(_lastLog.Text)) _lastLog.Text = _english ? "No session yet" : "尚無本次紀錄";
_latestButton.Text = _english ? "Load Latest" : "載入最近結果"; _browseButton.Text = _english ? "Choose Output Folder" : "選擇輸出資料夾"; _openSessionButton.Text = _english ? "Open Session Folder" : "開啟本次資料夾";
_analysisPathCaption.Text = _english ? "Loaded:" : "已載入:";
if (_analysisData == null && string.IsNullOrWhiteSpace(_analysisText.Text)) _analysisText.Text = _english ? "No session loaded. Select Load Latest or choose an output folder." : "尚未載入監測結果。請按「載入最近結果」或選擇輸出資料夾。";
}
private void ShowPage(bool analysis) { _analysisPage.Visible = analysis; _monitorPage.Visible = !analysis; if (analysis) _analysisPage.BringToFront(); else _monitorPage.BringToFront(); _analysisMenu.Checked = analysis; _monitorMenu.Checked = !analysis; if (analysis && _analysisData == null) LoadLatest(); }
private void LoadLatest() { string latest = SessionLoader.FindLatestSession(_logDirectory); if (string.IsNullOrWhiteSpace(latest)) { _analysisText.Text = _english ? "No monitoring session was found in the output folder." : "輸出資料夾中找不到監測工作階段。"; return; } LoadAnalysis(latest); }
private void BrowseFolder()
{
using (FolderBrowserDialog d = new FolderBrowserDialog()) { d.Description = _english ? "Choose a NetPulse session folder or the logs root folder." : "請選擇 NetPulse 單次監測資料夾,或 logs 根資料夾。"; d.SelectedPath = Directory.Exists(_logDirectory) ? _logDirectory : AppDomain.CurrentDomain.BaseDirectory; if (d.ShowDialog(this) == DialogResult.OK) LoadAnalysis(d.SelectedPath); }
}
private void LoadAnalysis(string path)
{
try { _analysisData = SessionLoader.Load(path); RenderAnalysis(); }
catch (Exception ex) { _analysisData = null; _analysisPath.Text = "--"; _analysisText.Text = (_english ? "Unable to load this session: " : "無法載入此工作階段:") + ex.Message; _chart.Series.Clear(); _chart.ChartAreas.Clear(); _chart.Legends.Clear(); }
}
private void RenderAnalysis() { if (_analysisData == null) return; _analysisPath.Text = _analysisData.SessionFolder; SessionChartBuilder.Populate(_chart, _analysisData, _english); _analysisText.Text = SessionAnalyzer.Analyze(_analysisData).BuildText(_english); _openSessionButton.Enabled = true; }
private void LoadSettings()
{
string key = "F8", process = "", language = "zh-TW"; bool control = true, alt = true, shift = false, win = false, global = true;
try { if (File.Exists(_settingsPath)) foreach (string raw in File.ReadAllLines(_settingsPath, System.Text.Encoding.UTF8))
{
string line = raw.Trim(); int sep = line.IndexOf('='); if (sep <= 0) continue; string name = line.Substring(0, sep).Trim(), value = line.Substring(sep + 1).Trim();
if (name.Equals("Key", StringComparison.OrdinalIgnoreCase)) key = value; else if (name.Equals("ProcessName", StringComparison.OrdinalIgnoreCase)) process = value; else if (name.Equals("Language", StringComparison.OrdinalIgnoreCase)) language = value;
else if (name.Equals("Ctrl", StringComparison.OrdinalIgnoreCase)) control = ParseBool(value, control); else if (name.Equals("Alt", StringComparison.OrdinalIgnoreCase)) alt = ParseBool(value, alt); else if (name.Equals("Shift", StringComparison.OrdinalIgnoreCase)) shift = ParseBool(value, shift); else if (name.Equals("Win", StringComparison.OrdinalIgnoreCase)) win = ParseBool(value, win); else if (name.Equals("Global", StringComparison.OrdinalIgnoreCase)) global = ParseBool(value, global);
}} catch { }
_hotkeyKey = ParseSavedKey(key); _hotkeyCtrl = control; _hotkeyAlt = alt; _hotkeyShift = shift; _hotkeyWin = win; _global.Checked = global; _local.Checked = !global;
_english = language.StartsWith("en", StringComparison.OrdinalIgnoreCase); _configuredProcess = process; RefreshProcesses(process);
_hotkeyBox.Text = HotkeyDisplay();
}
private void SaveSettings()
{
string[] lines = { "# NetPulse UI and hotkey settings", "Language=" + (_english ? "en-US" : "zh-TW"), "ProcessName=" + SelectedProcess, "Key=" + _hotkeyKey, "Ctrl=" + _hotkeyCtrl, "Alt=" + _hotkeyAlt, "Shift=" + _hotkeyShift, "Win=" + _hotkeyWin, "Global=" + _global.Checked };
File.WriteAllLines(_settingsPath, lines, new System.Text.UTF8Encoding(true));
}
private static bool ParseBool(string text, bool fallback) { bool value; return bool.TryParse(text, out value) ? value : fallback; }
private static Keys ParseSavedKey(string name)
{
if (string.IsNullOrWhiteSpace(name)) return Keys.F8;
if (name.Length == 1)
{
char character = char.ToUpperInvariant(name[0]);
if (character >= 'A' && character <= 'Z') return (Keys)character;
if (character >= '0' && character <= '9') return (Keys)((int)Keys.D0 + character - '0');
}
Keys parsed;
return Enum.TryParse<Keys>(name, true, out parsed) ? parsed : Keys.F8;
}
private static string KeyDisplay(Keys key)
{
if (key >= Keys.D0 && key <= Keys.D9) return ((int)key - (int)Keys.D0).ToString(CultureInfo.InvariantCulture);
if (key >= Keys.NumPad0 && key <= Keys.NumPad9) return "Num " + ((int)key - (int)Keys.NumPad0).ToString(CultureInfo.InvariantCulture);
switch (key)
{
case Keys.Oem1: return "; :";
case Keys.Oemplus: return "= +";
case Keys.Oemcomma: return ", <";
case Keys.OemMinus: return "- _";
case Keys.OemPeriod: return ". >";
case Keys.Oem2: return "/ ?";
case Keys.Oem3: return "` ~";
case Keys.Oem4: return "[ {";
case Keys.Oem5: return "\\ |";
case Keys.Oem6: return "] }";
case Keys.Oem7: return "' \"";
case Keys.Oem8: return "Symbol key";
case Keys.Oem102: return "\\ |";
case Keys.OemClear: return "Clear";
case Keys.Back: return "Backspace";
case Keys.Return: return "Enter";
case Keys.Escape: return "Esc";
case Keys.Space: return "Space";
case Keys.Capital: return "Caps Lock";
case Keys.Prior: return "Page Up";
case Keys.Next: return "Page Down";
case Keys.PrintScreen: return "Print Screen";
case Keys.Scroll: return "Scroll Lock";
case Keys.Pause: return "Pause";
case Keys.Insert: return "Insert";
case Keys.Delete: return "Delete";
case Keys.Home: return "Home";
case Keys.End: return "End";
case Keys.Left: return "Left Arrow";
case Keys.Right: return "Right Arrow";
case Keys.Up: return "Up Arrow";
case Keys.Down: return "Down Arrow";
case Keys.Decimal: return "Num .";
case Keys.Add: return "Num +";
case Keys.Subtract: return "Num -";
case Keys.Multiply: return "Num *";
case Keys.Divide: return "Num /";
}
string name = key.ToString();
if (name.StartsWith("Oem", StringComparison.OrdinalIgnoreCase)) return "Symbol key";
string friendly = "";
for (int i = 0; i < name.Length; i++)
{
if (i > 0 && char.IsUpper(name[i]) && char.IsLower(name[i - 1])) friendly += " ";
friendly += name[i];
}
return friendly;
}
private void CaptureHotkey(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.ControlKey || e.KeyCode == Keys.LControlKey || e.KeyCode == Keys.RControlKey ||
e.KeyCode == Keys.Menu || e.KeyCode == Keys.LMenu || e.KeyCode == Keys.RMenu ||
e.KeyCode == Keys.ShiftKey || e.KeyCode == Keys.LShiftKey || e.KeyCode == Keys.RShiftKey ||
e.KeyCode == Keys.LWin || e.KeyCode == Keys.RWin)
return;
_hotkeyKey = e.KeyCode;
_hotkeyCtrl = e.Control;
_hotkeyAlt = e.Alt;
_hotkeyShift = e.Shift;
_hotkeyWin = (GetAsyncKeyState((int)Keys.LWin) & 0x8000) != 0 || (GetAsyncKeyState((int)Keys.RWin) & 0x8000) != 0;
_hotkeyBox.Text = HotkeyDisplay();
_hotkeyCapturePending = true;
e.Handled = true;
e.SuppressKeyPress = true;
_hotkeyStatus.ForeColor = Color.FromArgb(35, 96, 145);
_hotkeyStatus.Text = (_english ? "Captured: " : "已偵測:") + HotkeyDisplay() + (_english ? ". Select Apply & Save to enable it." : "。請按「套用並儲存」啟用。");
}
private HotkeyChoice SelectedKey
{
get { return new HotkeyChoice(KeyDisplay(_hotkeyKey), _hotkeyKey); }
}
private string SelectedProcess { get { ProcessChoice choice = _processCombo.SelectedItem as ProcessChoice; return choice == null ? _configuredProcess : choice.ProcessName; } }
private void RefreshProcesses(string preferred)
{
Dictionary<string, ProcessWindowInfo> found = new Dictionary<string, ProcessWindowInfo>(StringComparer.OrdinalIgnoreCase); Process[] list; try { list = Process.GetProcesses(); } catch { list = new Process[0]; }
foreach (Process p in list) { try { string name = p.ProcessName, title = p.MainWindowTitle; if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(title) || !IsTaskbarWindow(p.MainWindowHandle) || name.Equals("Idle", StringComparison.OrdinalIgnoreCase) || name.Equals("System", StringComparison.OrdinalIgnoreCase) || name.Equals("Registry", StringComparison.OrdinalIgnoreCase) || name.Equals("TextInputHost", StringComparison.OrdinalIgnoreCase) || name.Equals("NetPulse", StringComparison.OrdinalIgnoreCase)) continue; ProcessWindowInfo info; if (!found.TryGetValue(name, out info)) { info = new ProcessWindowInfo { WindowTitle = title.Trim() }; found[name] = info; } info.WindowCount++; } catch { } finally { p.Dispose(); } }
List<ProcessChoice> choices = new List<ProcessChoice>();
foreach (KeyValuePair<string, ProcessWindowInfo> pair in found) { string title = pair.Value.WindowTitle; if (title.Length > 60) title = title.Substring(0, 60) + "…"; string display = pair.Key + ".exe — " + title; if (pair.Value.WindowCount > 1) display += _english ? " (" + pair.Value.WindowCount + " windows)" : "(" + pair.Value.WindowCount + " 個視窗)"; choices.Add(new ProcessChoice(pair.Key, display)); }
choices.Sort(delegate(ProcessChoice a, ProcessChoice b) { return string.Compare(a.ProcessName, b.ProcessName, StringComparison.CurrentCultureIgnoreCase); });
choices.Insert(0, new ProcessChoice("", _english ? "No target — monitor CPU and network only" : "不選擇目標—僅監測 CPU 與網路"));
_processCombo.BeginUpdate(); _processCombo.Items.Clear(); int selected = -1;
for (int i = 0; i < choices.Count; i++) { _processCombo.Items.Add(choices[i]); if (choices[i].ProcessName.Equals(preferred ?? "", StringComparison.OrdinalIgnoreCase)) selected = i; }
_processCombo.SelectedIndex = selected >= 0 ? selected : 0; _processCombo.EndUpdate();
}
private static bool IsTaskbarWindow(IntPtr h) { if (h == IntPtr.Zero || !IsWindowVisible(h)) return false; long style; try { style = GetWindowLongPtr(h, GwlExStyle).ToInt64(); } catch { return false; } bool tool = (style & WsExToolWindow) != 0, app = (style & WsExAppWindow) != 0, owner = GetWindow(h, GwOwner) != IntPtr.Zero; return !tool && (!owner || app); }
private void ApplyHotkey(bool save)
{
if (_registered) { UnregisterHotKey(Handle, HotkeyId); _registered = false; } if (_local.Checked && _hotkeyWin) _hotkeyWin = false;
_hotkeyCapturePending = false;
_hotkeyBox.Text = HotkeyDisplay();
if (_global.Checked) { _registered = RegisterHotKey(Handle, HotkeyId, NativeModifiers() | ModNoRepeat, (uint)SelectedKey.Key); _hotkeyStatus.ForeColor = _registered ? Color.FromArgb(35, 126, 80) : Color.FromArgb(190, 61, 61); _hotkeyStatus.Text = _registered ? (_english ? "Global hotkey enabled: " : "全域快捷鍵已啟用:") + HotkeyDisplay() : (_english ? "This hotkey is probably used by another app." : "此快捷鍵可能被其他程式占用,請更換後再套用。"); }
else { _hotkeyStatus.ForeColor = Color.FromArgb(70, 90, 108); _hotkeyStatus.Text = (_english ? "Window-only hotkey: " : "僅視窗內偵測:") + HotkeyDisplay(); }
if (save) try { SaveSettings(); } catch (Exception ex) { _hotkeyStatus.Text = (_english ? "Unable to save settings: " : "設定無法儲存:") + ex.Message; }
}
private uint NativeModifiers() { uint value = 0; if (_hotkeyCtrl) value |= ModControl; if (_hotkeyAlt) value |= ModAlt; if (_hotkeyShift) value |= ModShift; if (_hotkeyWin) value |= ModWin; return value; }
private string HotkeyDisplay() { List<string> p = new List<string>(); if (_hotkeyCtrl) p.Add("Ctrl"); if (_hotkeyAlt) p.Add("Alt"); if (_hotkeyShift) p.Add("Shift"); if (_hotkeyWin) p.Add("Win"); p.Add(SelectedKey.DisplayName); return string.Join(" + ", p.ToArray()); }
protected override void WndProc(ref Message m) { if (m.Msg == WmHotkey && m.WParam.ToInt32() == HotkeyId) { AddMarker(_english ? "global hotkey" : "全域快捷鍵"); return; } base.WndProc(ref m); }
protected override bool ProcessCmdKey(ref Message m, Keys keyData) { if (_hotkeyBox != null && _hotkeyBox.Focused) return base.ProcessCmdKey(ref m, keyData); if (_local != null && _local.Checked && IsLocalHotkey(keyData)) { if ((DateTime.Now - _lastLocalHotkey).TotalMilliseconds >= 300) { _lastLocalHotkey = DateTime.Now; AddMarker(_english ? "window hotkey" : "視窗快捷鍵"); } return true; } return base.ProcessCmdKey(ref m, keyData); }
private bool IsLocalHotkey(Keys data) { Keys expected = Keys.None; if (_hotkeyCtrl) expected |= Keys.Control; if (_hotkeyAlt) expected |= Keys.Alt; if (_hotkeyShift) expected |= Keys.Shift; return (data & Keys.KeyCode) == SelectedKey.Key && (data & Keys.Modifiers) == expected; }
private void StartMonitoring(bool markFirstSample = false)
{
if (_running) return; string target = SelectedProcess;
_configuredProcess = target; try { SaveSettings(); } catch { } Directory.CreateDirectory(_logDirectory); _running = true; _closeAfterStop = false; _markerCount = markFirstSample ? 1 : 0; _sessionStarted = DateTime.Now;
_state.Text = _english ? "Monitoring" : "監測中"; _state.ForeColor = Color.FromArgb(35, 126, 80); _markers.Text = _markerCount.ToString(CultureInfo.InvariantCulture); _lastLog.Text = _english ? "Writing this session…" : "正在寫入本次紀錄…";
_startButton.Enabled = false; _stopButton.Enabled = true; _markButton.Enabled = true; _processCombo.Enabled = false; _refreshButton.Enabled = false; _target.Text = string.IsNullOrWhiteSpace(target) ? (_english ? "Not selected — CPU and network only" : "未選擇—僅監測 CPU 與網路") : target + ".exe: " + (_english ? "waiting for sample…" : "等待取樣…");
Thread thread = new Thread(new ThreadStart(delegate { Program.RunEmbedded(target, OnSample, OnFinished, markFirstSample); })); thread.IsBackground = true; thread.Name = "NetPulse Monitor"; thread.Start();
}
private void StopMonitoring() { if (!_running) return; _state.Text = _english ? "Stopping and creating summary…" : "正在停止並產生摘要…"; _stopButton.Enabled = false; _markButton.Enabled = false; Program.RequestStop(); }
private void AddMarker(string source)
{
if (!_running)
{
StartMonitoring(true);
if (!_running) return;
_hotkeyStatus.ForeColor = Color.FromArgb(35, 126, 80);
_hotkeyStatus.Text = (_english ? "Detected " : "已偵測到 ") + source + (_english ? "; monitoring started automatically. The first sample will be marked." : ";已自動開始監測,第一筆取樣將加入標記。");
System.Media.SystemSounds.Asterisk.Play();
return;
}
RecordMarker();
}
private void RecordMarker()
{
Program.RequestMarker(); _markerCount++; _markers.Text = _markerCount + (_english ? "; last " : " 次;最後 ") + DateTime.Now.ToString("HH:mm:ss"); System.Media.SystemSounds.Asterisk.Play();
}
private void OnSample(Sample s)
{
if (!IsHandleCreated || IsDisposed) return; try { BeginInvoke((MethodInvoker)delegate
{
bool hasTarget = !string.IsNullOrWhiteSpace(s.TargetProcessName);
_mode.Text = !hasTarget ? (_english ? "CPU and network monitoring" : "CPU 與網路監測") : s.Valorant.IsRunning ? (_english ? "Target is running" : "目標程式執行中") : (_english ? "Waiting for target" : "等待目標程式"); _mode.ForeColor = s.Valorant.IsRunning ? Color.FromArgb(188, 78, 42) : Color.FromArgb(36, 50, 66);
_cpu.Text = string.Format(CultureInfo.InvariantCulture, _english ? "Total {0:0.0}% / max core {1:0.0}% / busy cores {2}" : "總計 {0:0.0}%/最高核心 {1:0.0}%/忙碌核心 {2}", s.Cpu.TotalCpuPercent, s.Cpu.MaxLogicalCpuPercent, s.Cpu.BusyLogicalCores);
_target.Text = !hasTarget ? (_english ? "Not selected — CPU and network only" : "未選擇—僅監測 CPU 與網路") : s.TargetProcessName + ".exe: " + (s.Valorant.IsRunning ? string.Format(CultureInfo.InvariantCulture, _english ? "running / CPU {0:0.0}%" : "執行中/CPU {0:0.0}%", s.Valorant.CpuPercent) : (_english ? "not running (still waiting)" : "目前未執行(持續等待)"));
string gateway = s.Network.GatewayPing.Success ? s.Network.GatewayPing.RoundtripMs + "ms" : (_english ? "timeout" : "逾時"), internet = s.Network.InternetPing.Success ? s.Network.InternetPing.RoundtripMs + "ms" : (_english ? "timeout" : "逾時");
_network.Text = string.Format(CultureInfo.InvariantCulture, _english ? "Gateway {0} / Internet {1} / jitter {2:0.0}ms / 60-sample loss {3:0.0}%" : "路由器 {0}/外網 {1}/Jitter {2:0.0}ms/60 筆丟包 {3:0.0}%", gateway, internet, s.Network.InternetJitterMs, s.Network.InternetLossPercent);
_traffic.Text = string.Format(CultureInfo.InvariantCulture, _english ? "Down {0:0.0} / up {1:0.0} KB/s" : "下載 {0:0.0}/上傳 {1:0.0} KB/s", s.Network.DownloadKBps, s.Network.UploadKBps);
}); } catch { }
}
private void OnFinished(SessionResult result)
{
if (!IsHandleCreated || IsDisposed) return; try { BeginInvoke((MethodInvoker)delegate
{
_running = false; _startButton.Enabled = true; _stopButton.Enabled = false; _markButton.Enabled = false; _processCombo.Enabled = true; _refreshButton.Enabled = true; RefreshProcesses(result.TargetProcessName);
if (string.IsNullOrWhiteSpace(result.ErrorMessage)) { _state.Text = _english ? "Stopped" : "已停止"; _state.ForeColor = Color.FromArgb(90, 100, 112); _lastLog.Text = result.SessionDirectory; LoadAnalysis(result.SessionDirectory); }
else { _state.Text = _english ? "Error" : "發生錯誤"; _state.ForeColor = Color.FromArgb(190, 61, 61); _lastLog.Text = result.ErrorMessage; }
if (_closeAfterStop) { _closeAfterStop = false; Close(); }
}); } catch { }
}
private static void OpenFolder(string path) { Directory.CreateDirectory(path); Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true }); }
private void OnClosing(object sender, FormClosingEventArgs e) { if (_running) { e.Cancel = true; _closeAfterStop = true; StopMonitoring(); return; } if (_registered) { UnregisterHotKey(Handle, HotkeyId); _registered = false; } }
private static string Duration(TimeSpan d) { return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}:{2:00}", (int)d.TotalHours, d.Minutes, d.Seconds); }
}
internal sealed class HotkeyChoice { public string DisplayName { get; private set; } public Keys Key { get; private set; } public HotkeyChoice(string name, Keys key) { DisplayName = name; Key = key; } public override string ToString() { return DisplayName; } }
internal sealed class ProcessChoice { public string ProcessName { get; private set; } public string DisplayText { get; private set; } public ProcessChoice(string name, string text) { ProcessName = name; DisplayText = text; } public override string ToString() { return DisplayText; } }
internal sealed class ProcessWindowInfo { public int WindowCount; public string WindowTitle; }
}