using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; using System.Windows.Forms; namespace ClaudeProxy { class BufferedPanel : Panel { public BufferedPanel() { DoubleBuffered = true; } } class MainForm : Form { // palette static Color Cream = Color.FromArgb(250, 249, 245), Ink = Color.FromArgb(41, 40, 36), Head = Color.FromArgb(26, 25, 21), Muted = Color.FromArgb(131, 129, 123), Card = Color.FromArgb(255, 255, 255), Border = Color.FromArgb(232, 229, 220), Coral = Color.FromArgb(193, 95, 60), CoralLo = Color.FromArgb(224, 137, 101), CoralHi = Color.FromArgb(168, 79, 48), TrackOff = Color.FromArgb(206, 202, 192), OkC = Color.FromArgb(74, 122, 80), RedC = Color.FromArgb(196, 74, 61), Glyph = Color.FromArgb(248, 246, 239); Font fReg = new Font("Segoe UI", 9.5f), fSemi = new Font("Segoe UI Semibold", 9.5f), fHead = new Font("Segoe UI Semibold", 15f), fSub = new Font("Segoe UI", 9f), fPill = new Font("Segoe UI Semibold", 12f), fMono = new Font("Consolas", 8.5f); bool switchOn = false, busy = false; RadioButton rbProxy, rbVless, rbAwg; BufferedPanel mark, tgl, card; Label title, subtitle, lblFieldCap, lblReach, rDot, lblFwCap, lblFw, lblClCap, lblCl, lblXrayCap, lblXray; TextBox txtField, log; CheckBox chkRestart, chkFail; Button btnSave, btnLaunch, btnVerify, btnRefresh, btnXray; Panel sep; Timer timer; string Mode { get { return rbAwg.Checked ? "awg" : (rbVless.Checked ? "vless" : "proxy"); } } public MainForm() { Text = "Claude Proxy"; AutoScaleMode = AutoScaleMode.None; ClientSize = new Size(452, 512); StartPosition = FormStartPosition.CenterScreen; FormBorderStyle = FormBorderStyle.FixedSingle; MaximizeBox = false; BackColor = Cream; ForeColor = Ink; Font = fReg; try { Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); } catch { } mark = new BufferedPanel { Size = new Size(46, 46), Location = new Point(24, 22), BackColor = Cream }; mark.Paint += (s, e) => PaintTile(e.Graphics, 46); Controls.Add(mark); title = MkLabel("Claude Proxy", fHead, Head, 82, 22); title.AutoSize = true; Controls.Add(title); subtitle = MkLabel("весь трафик Claude через прокси", fSub, Muted, 84, 48); subtitle.AutoSize = false; subtitle.Size = new Size(196, 18); Controls.Add(subtitle); rbProxy = MkRadio("Прямой прокси", 292, 14); rbVless = MkRadio("VLESS + Reality", 292, 36); rbAwg = MkRadio("AmneziaWG", 292, 58); Controls.Add(rbProxy); Controls.Add(rbVless); Controls.Add(rbAwg); tgl = new BufferedPanel { Size = new Size(172, 58), Location = new Point(24, 88), BackColor = Cream, Cursor = Cursors.Hand }; tgl.Paint += (s, e) => PaintToggle(e.Graphics, tgl.Width, tgl.Height); tgl.Click += (s, e) => ToggleSwitch(); Controls.Add(tgl); var hint = MkLabel("Включено = наружу только через прокси, всё остальное заблокировано", fSub, Muted, 208, 92); hint.AutoSize = false; hint.Size = new Size(224, 52); Controls.Add(hint); // status card card = new BufferedPanel { Size = new Size(412, 158), Location = new Point(20, 162), BackColor = Cream }; card.Paint += (s, e) => { var g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; using (var path = Round(card.Width - 1, card.Height - 1, 14)) using (var b = new SolidBrush(Card)) using (var pen = new Pen(Border, 1)) { g.FillPath(b, path); g.DrawPath(pen, path); } }; Controls.Add(card); lblFieldCap = MkLabel("Прокси", fReg, Muted, 18, 18); lblFieldCap.AutoSize = true; card.Controls.Add(lblFieldCap); txtField = new TextBox { Location = new Point(84, 15), Size = new Size(214, 24), BorderStyle = BorderStyle.FixedSingle, BackColor = Cream, ForeColor = Ink }; card.Controls.Add(txtField); btnSave = MkButton("Сохранить", 308, 13, 88, 28, false); btnSave.Click += (s, e) => OnSave(); card.Controls.Add(btnSave); rDot = MkLabel("●", new Font("Segoe UI", 11f), Muted, 17, 54); rDot.AutoSize = true; card.Controls.Add(rDot); lblReach = MkLabel("", fReg, Ink, 38, 56); lblReach.AutoSize = true; card.Controls.Add(lblReach); lblXrayCap = MkLabel("Xray-core", fReg, Muted, 18, 68); lblXrayCap.AutoSize = true; card.Controls.Add(lblXrayCap); lblXray = MkLabel("", fSemi, Ink, 120, 68); lblXray.AutoSize = true; card.Controls.Add(lblXray); btnXray = MkButton("Распаковать", 300, 64, 96, 26, false); btnXray.Click += (s, e) => { if (Mode == "awg") OnBrowseAwg(); else OnXray(); }; card.Controls.Add(btnXray); sep = new Panel { Location = new Point(18, 86), Size = new Size(378, 1), BackColor = Border }; card.Controls.Add(sep); lblFwCap = MkLabel("Firewall-лок", fReg, Muted, 18, 98); lblFwCap.AutoSize = true; card.Controls.Add(lblFwCap); lblFw = MkLabel("", fSemi, Ink, 120, 98); lblFw.AutoSize = true; card.Controls.Add(lblFw); lblClCap = MkLabel("Claude", fReg, Muted, 18, 122); lblClCap.AutoSize = true; card.Controls.Add(lblClCap); lblCl = MkLabel("", fSemi, Ink, 120, 122); lblCl.AutoSize = true; card.Controls.Add(lblCl); chkRestart = new CheckBox { Text = "Перезапускать Claude при переключении", ForeColor = Ink, AutoSize = true, Checked = true, Location = new Point(22, 324), Font = fReg }; chkFail = new CheckBox { Text = "Fail-closed: блокировать любой трафик мимо прокси (firewall)", ForeColor = Ink, AutoSize = true, Checked = true, Location = new Point(22, 348), Font = fReg }; Controls.Add(chkRestart); Controls.Add(chkFail); btnLaunch = MkButton("Запустить Claude", 20, 384, 170, 36, true); btnLaunch.Click += (s, e) => LaunchProxied(); Controls.Add(btnLaunch); btnVerify = MkButton("Проверить", 198, 386, 142, 32, false); btnVerify.Click += (s, e) => OnVerify(); Controls.Add(btnVerify); btnRefresh = MkButton("Обновить", 348, 386, 84, 32, false); btnRefresh.Click += (s, e) => { UpdateUi(true); Log("статус обновлён."); }; Controls.Add(btnRefresh); log = new TextBox { Multiline = true, ReadOnly = true, ScrollBars = ScrollBars.Vertical, Location = new Point(20, 430), Size = new Size(412, 66), BorderStyle = BorderStyle.FixedSingle, BackColor = Card, ForeColor = Muted, Font = fMono }; Controls.Add(log); EventHandler modeCh = (s, e) => { if (!((RadioButton)s).Checked) return; ApplyModeLayout(); var c = Core.LoadConfig(); c.Mode = Mode; Core.SaveConfig(c); UpdateUi(true); }; rbVless.CheckedChanged += modeCh; rbAwg.CheckedChanged += modeCh; rbProxy.CheckedChanged += modeCh; timer = new Timer { Interval = 4000 }; timer.Tick += (s, e) => { if (!busy) UpdateUi(false); }; timer.Start(); Load += (s, e) => { var cfg = Core.LoadConfig(); chkFail.Checked = cfg.FailClosed; if (cfg.Mode == "awg") rbAwg.Checked = true; else if (cfg.Mode == "vless") rbVless.Checked = true; else rbProxy.Checked = true; ApplyModeLayout(); Log("готово. рубильник управляет проксированием Claude."); UpdateUi(true); }; } // ---- factories ---- Label MkLabel(string t, Font f, Color c, int x, int y) { return new Label { Text = t, Font = f, ForeColor = c, Location = new Point(x, y), BackColor = Color.Transparent }; } RadioButton MkRadio(string t, int x, int y) { return new RadioButton { Text = t, ForeColor = Ink, Font = fReg, AutoSize = false, TextAlign = ContentAlignment.MiddleLeft, BackColor = Cream, Size = new Size(150, 22), Location = new Point(x, y) }; } Button MkButton(string t, int x, int y, int w, int h, bool primary) { var b = new Button { Text = t, Location = new Point(x, y), Size = new Size(w, h), FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand }; b.FlatAppearance.BorderSize = primary ? 0 : 1; if (primary) { b.BackColor = Coral; b.ForeColor = Color.White; b.Font = fSemi; b.FlatAppearance.MouseOverBackColor = CoralHi; } else { b.BackColor = Card; b.ForeColor = Ink; b.Font = fReg; b.FlatAppearance.BorderColor = Border; b.FlatAppearance.MouseOverBackColor = Cream; } b.Region = new Region(Round(w, h, 9)); b.SizeChanged += (s, e) => { try { b.Region = new Region(Round(b.Width, b.Height, 9)); } catch { } }; return b; } static GraphicsPath Round(int w, int h, int r) { int d = r * 2; var p = new GraphicsPath(); p.AddArc(0, 0, d, d, 180, 90); p.AddArc(w - d, 0, d, d, 270, 90); p.AddArc(w - d, h - d, d, d, 0, 90); p.AddArc(0, h - d, d, d, 90, 90); p.CloseFigure(); return p; } // ---- painting ---- void PaintTile(Graphics g, int S) { g.SmoothingMode = SmoothingMode.AntiAlias; using (var path = Round(S - 1, S - 1, 12)) using (var br = new LinearGradientBrush(new Rectangle(0, 0, S, S), CoralLo, CoralHi, 90f)) g.FillPath(br, path); DrawToggleGlyph(g, S); } void DrawToggleGlyph(Graphics g, float S) { float tw = S * 0.58f, th = S * 0.34f, tx = (S - tw) / 2f, ty = (S - th) / 2f; using (var tp = new GraphicsPath()) { tp.AddArc(tx, ty, th, th, 90, 180); tp.AddArc(tx + tw - th, ty, th, th, 270, 180); tp.CloseFigure(); using (var tb = new SolidBrush(Glyph)) g.FillPath(tb, tp); } float kd = th - S * 0.10f, kx = tx + tw - kd - S * 0.05f, ky = ty + (th - kd) / 2f; using (var kb = new SolidBrush(CoralHi)) g.FillEllipse(kb, kx, ky, kd, kd); } void PaintToggle(Graphics g, int w, int h) { g.SmoothingMode = SmoothingMode.AntiAlias; using (var path = Round(w, h, h / 2)) { if (switchOn) using (var br = new LinearGradientBrush(new Rectangle(0, 0, w, h), CoralLo, Coral, 0f)) g.FillPath(br, path); else using (var br = new SolidBrush(TrackOff)) g.FillPath(br, path); } int knobD = h - 12, knobX = switchOn ? w - knobD - 6 : 6; using (var sh = new SolidBrush(Color.FromArgb(40, 0, 0, 0))) g.FillEllipse(sh, knobX + 1, 7, knobD, knobD); using (var kb = new SolidBrush(Color.White)) g.FillEllipse(kb, knobX, 6, knobD, knobD); string txt = switchOn ? "ВКЛ" : "ВЫКЛ"; using (var tb = new SolidBrush(switchOn ? Color.White : Color.FromArgb(106, 104, 98))) g.DrawString(txt, fPill, tb, switchOn ? 22 : 66, 16); } // ---- mode layout ---- void ApplyModeLayout() { var cfg = Core.LoadConfig(); bool tunnel = Mode == "vless" || Mode == "awg"; lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = tunnel; if (tunnel) { sep.Top = 100; lblFwCap.Top = lblFw.Top = 112; lblClCap.Top = lblCl.Top = 134; } else { sep.Top = 74; lblFwCap.Top = lblFw.Top = 86; lblClCap.Top = lblCl.Top = 108; } if (Mode == "vless") { lblFieldCap.Text = "VLESS"; subtitle.Text = "через VLESS + Reality тоннель"; txtField.UseSystemPasswordChar = true; txtField.Text = cfg.Vless ?? ""; lblXrayCap.Text = "Xray-core"; UpdateXrayStatus(); } else if (Mode == "awg") { lblFieldCap.Text = "Конфиг"; subtitle.Text = "через AmneziaWG тоннель"; txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Awg ?? ""; lblXrayCap.Text = "AmneziaWG"; UpdateAwgStatus(); } else { lblFieldCap.Text = "Прокси"; subtitle.Text = "весь трафик Claude через прокси"; txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Proxy ?? ""; } } void UpdateXrayStatus() { if (Core.XrayPresent()) { lblXray.Text = "готов (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Переустановить"; } else { lblXray.Text = "встроен (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Распаковать"; } btnXray.Width = 96; btnXray.Left = 300; } void UpdateAwgStatus() { bool ok = !string.IsNullOrWhiteSpace(txtField.Text) && System.IO.File.Exists(txtField.Text.Trim()); lblXray.Text = ok ? "конфиг выбран" : "не выбран"; lblXray.ForeColor = ok ? OkC : RedC; btnXray.Text = "Обзор..."; btnXray.Width = 96; btnXray.Left = 300; } // ---- helpers ---- void Log(string m) { log.AppendText(DateTime.Now.ToString("HH:mm:ss") + " " + m + "\r\n"); } void SetBusy(bool b) { busy = b; btnSave.Enabled = btnRefresh.Enabled = btnVerify.Enabled = btnLaunch.Enabled = btnXray.Enabled = !b; UpdateModeLock(); Cursor = b ? Cursors.WaitCursor : Cursors.Default; } // Mode can't be switched while the lock is active (ON) or during an operation. void UpdateModeLock() { rbProxy.Enabled = rbVless.Enabled = rbAwg.Enabled = !switchOn && !busy; } Config CaptureConfig() { var c = Core.LoadConfig(); c.Mode = Mode; c.FailClosed = chkFail.Checked; if (Mode == "vless") c.Vless = txtField.Text; else if (Mode == "awg") c.Awg = txtField.Text; else c.Proxy = txtField.Text; Core.SaveConfig(c); return c; } void UpdateUi(bool checkReach) { try { ClaudeApp app = null; try { app = Core.GetClaudeApp(); } catch { } string fw = app != null ? Core.FirewallState(app.Exe) : Core.FirewallState(null); bool newOn = fw == "ok"; if (newOn != switchOn) { switchOn = newOn; tgl.Invalidate(); } UpdateModeLock(); lblFw.Text = fw == "ok" ? "установлен" : (fw == "stale" ? "устарел (путь изменился)" : "нет"); lblFw.ForeColor = fw == "ok" ? Coral : (fw == "stale" ? RedC : Muted); bool running = Core.ClaudeRunning(); if (!running) { lblCl.Text = "не запущен"; lblCl.ForeColor = Muted; } else { lblCl.Text = "запущен"; lblCl.ForeColor = Ink; } if (checkReach) { bool ok; string text; if (Mode == "vless") { string ep; ok = Vless.ServerReachable(txtField.Text, out ep); text = (ok ? "сервер доступен " : "не отвечает ") + ep; } else if (Mode == "awg") { string ep; ok = Core.AwgConfigInfo(txtField.Text, out ep); text = ok ? ("конфиг: " + ep) : "конфиг не выбран"; } else { string h; int p; if (Core.TryParseProxy(txtField.Text, out h, out p)) { ok = Core.TcpReachable(h, p, 1500); text = (ok ? "сервер доступен " : "не отвечает ") + h + ":" + p; } else { ok = false; text = "некорректный адрес"; } } rDot.ForeColor = ok ? OkC : RedC; lblReach.Text = text; } if (Mode == "vless") UpdateXrayStatus(); else if (Mode == "awg") UpdateAwgStatus(); } catch (Exception ex) { Log("ошибка обновления: " + ex.Message); } } // ---- orchestration ---- bool Preflight() { if (Mode == "vless") { if (string.IsNullOrWhiteSpace(txtField.Text)) { MessageBox.Show("Вставь VLESS-ссылку из 3x-ui.", "Нужна ссылка", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; } try { string h, u, r; int p; Vless.Parse(txtField.Text, out h, out p, out u, out r); } catch (Exception ex) { MessageBox.Show(ex.Message, "Некорректная ссылка", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } if (!Core.XrayPresent()) { SetBusy(true); try { Core.EnsureXray(); UpdateXrayStatus(); } catch (Exception ex) { Log("ОШИБКА Xray: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка Xray", MessageBoxButtons.OK, MessageBoxIcon.Error); SetBusy(false); return false; } SetBusy(false); } string ep; if (!Vless.ServerReachable(txtField.Text, out ep)) if (MessageBox.Show("Сервер " + ep + " не отвечает. Всё равно продолжить?", "Сервер недоступен", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return false; } else if (Mode == "awg") { if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim())) { MessageBox.Show("Выбери файл AmneziaWG .conf (кнопка «Обзор...»).", "Нужен конфиг", MessageBoxButtons.OK, MessageBoxIcon.Warning); return false; } if (!Core.AwgPresent()) { SetBusy(true); try { Core.EnsureAwg(); UpdateAwgStatus(); } catch (Exception ex) { Log("ОШИБКА AmneziaWG: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); SetBusy(false); return false; } SetBusy(false); } } else { string h; int p; if (!Core.TryParseProxy(txtField.Text, out h, out p)) { MessageBox.Show("Некорректный адрес прокси.", "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } if (!Core.TcpReachable(h, p, 1500)) if (MessageBox.Show("Прокси " + h + ":" + p + " не отвечает. Всё равно включить?", "Прокси недоступен", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return false; } CaptureConfig(); return true; } void DoEnable(bool restart) { var cfg = Core.LoadConfig(); string proxyArg; if (cfg.Mode == "vless") { Core.EnsureXray(); Core.StartXray(cfg.Vless, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; } else if (cfg.Mode == "awg") { Core.EnsureAwg(); Core.StartAwg(cfg.Awg, cfg.LocalPort); proxyArg = "socks5://127.0.0.1:" + cfg.LocalPort; } else proxyArg = Core.ProxyArg(cfg.Proxy); if (cfg.FailClosed) if (!Core.ElevateSelf("fwapply")) { Core.StopXray(); Core.StopAwg(); throw new Exception("Установка firewall отменена в UAC."); } if (restart) { Core.StopClaude(); string a = "--proxy-server=" + proxyArg; Core.LaunchClaude(a); } } void DoDisable(bool restart) { string exe = null; try { exe = Core.GetClaudeApp().Exe; } catch { } if (Core.FirewallState(exe) != "absent") if (!Core.ElevateSelf("fwremove")) throw new Exception("Снятие firewall отменено в UAC (лок остался)."); Core.StopXray(); Core.StopAwg(); if (restart) { Core.StopClaude(); Core.LaunchClaude(""); } } void ToggleSwitch() { if (busy) return; bool restart = chkRestart.Checked; try { if (!switchOn) { if (!Preflight()) return; bool running = Core.ClaudeRunning(); bool doRestart = false; if (running) { if (restart) { if (MessageBox.Show("Claude запущен. Перезапустить его через прокси? (текущее окно закроется)", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) return; doRestart = true; } } else doRestart = true; SetBusy(true); Log("включаю..."); DoEnable(doRestart); Log("ВКЛ. " + (!running ? "Claude запущен через прокси." : (doRestart ? "Claude перезапущен." : "лок установлен; перезапусти Claude, чтобы применить."))); } else { bool restartOff = restart; if (restartOff && MessageBox.Show("Выключение перезапустит Claude напрямую (текущее окно закроется). Продолжить?", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) restartOff = false; SetBusy(true); Log("выключаю прокси..."); DoDisable(restartOff); Log("прокси ВЫКЛ. firewall-лок снят."); } } catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { SetBusy(false); UpdateUi(true); } } void LaunchProxied() { if (busy) return; try { if (!Preflight()) return; if (Core.ClaudeRunning() && MessageBox.Show("Claude уже запущен. Перезапустить его через прокси? (текущее окно закроется)", "Перезапуск Claude", MessageBoxButtons.OKCancel, MessageBoxIcon.Question) != DialogResult.OK) return; SetBusy(true); Log("запускаю Claude через прокси..."); DoEnable(true); Log("Claude запущен через прокси."); } catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { SetBusy(false); UpdateUi(true); } } void OnVerify() { try { var r = Verify.Run(Core.LoadConfig()); foreach (var l in r.Lines) Log(l); string stamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); string p = Verify.WriteLog(r, stamp); if (p.Length > 0) Log("лог: " + p); MessageBox.Show(r.Verdict, "Результат проверки", MessageBoxButtons.OK, r.Ok ? MessageBoxIcon.Information : (r.Verdict.StartsWith("ПРОВАЛ") ? MessageBoxIcon.Error : MessageBoxIcon.Warning)); } catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } void OnSave() { try { if (Mode == "vless") { string h, u, rm; int p; Vless.Parse(txtField.Text, out h, out p, out u, out rm); CaptureConfig(); Log("VLESS сохранён: " + h + ":" + p + " [" + rm + "]"); } else if (Mode == "awg") { if (string.IsNullOrWhiteSpace(txtField.Text) || !System.IO.File.Exists(txtField.Text.Trim())) throw new Exception("Файл AmneziaWG-конфига не найден."); CaptureConfig(); Log("AmneziaWG-конфиг сохранён: " + txtField.Text.Trim()); } else { string h; int p; if (!Core.TryParseProxy(txtField.Text, out h, out p)) throw new Exception("Некорректный адрес."); CaptureConfig(); Log("прокси сохранён: " + Core.ProxyArg(txtField.Text)); } if (switchOn) Log("ВНИМАНИЕ: режим активен со старой настройкой — переключи рубильник, чтобы применить."); UpdateUi(true); } catch (Exception ex) { MessageBox.Show(ex.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } void OnXray() { if (busy) return; try { SetBusy(true); Core.StopXray(); try { if (Core.XrayPresent()) System.IO.File.Delete(Core.XrayExe); } catch { } Log("готовлю Xray-core..."); Core.EnsureXray(); UpdateXrayStatus(); Log("Xray готов."); } catch (Exception ex) { Log("ОШИБКА: " + ex.Message); MessageBox.Show(ex.Message, "Ошибка Xray", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { SetBusy(false); } } void OnBrowseAwg() { using (var dlg = new OpenFileDialog()) { dlg.Title = "Выбери AmneziaWG .conf"; dlg.Filter = "AmneziaWG config (*.conf)|*.conf|Все файлы (*.*)|*.*"; if (dlg.ShowDialog() != DialogResult.OK) return; txtField.Text = dlg.FileName; CaptureConfig(); UpdateAwgStatus(); Log("конфиг выбран: " + dlg.FileName); UpdateUi(true); } } } }