claude-proxy: native C# GUI to force Claude desktop traffic through a proxy
Windows tray "switch" that routes all Claude MSIX-app traffic through a SOCKS5/HTTP proxy or a VLESS+Reality tunnel (embedded Xray-core), with a fail-closed Windows Firewall lock. Runs de-elevated so it launches the user's normal Claude instance; elevates only for firewall changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,391 @@
|
||||
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;
|
||||
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 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, 18);
|
||||
rbVless = MkRadio("VLESS + Reality", 292, 42);
|
||||
Controls.Add(rbProxy); Controls.Add(rbVless);
|
||||
|
||||
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) => 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);
|
||||
|
||||
rbVless.CheckedChanged += (s, e) => { ApplyModeLayout(); var c = Core.LoadConfig(); c.Mode = Mode; Core.SaveConfig(c); UpdateUi(true); };
|
||||
|
||||
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 == "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();
|
||||
if (Mode == "vless")
|
||||
{
|
||||
lblFieldCap.Text = "VLESS"; subtitle.Text = "через VLESS + Reality тоннель";
|
||||
txtField.UseSystemPasswordChar = true; txtField.Text = cfg.Vless ?? "";
|
||||
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = true;
|
||||
sep.Top = 100; lblFwCap.Top = lblFw.Top = 112; lblClCap.Top = lblCl.Top = 134;
|
||||
UpdateXrayStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
lblFieldCap.Text = "Прокси"; subtitle.Text = "весь трафик Claude через прокси";
|
||||
txtField.UseSystemPasswordChar = false; txtField.Text = cfg.Proxy ?? "";
|
||||
lblXrayCap.Visible = lblXray.Visible = btnXray.Visible = false;
|
||||
sep.Top = 74; lblFwCap.Top = lblFw.Top = 86; lblClCap.Top = lblCl.Top = 108;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateXrayStatus()
|
||||
{
|
||||
if (Core.XrayPresent()) { lblXray.Text = "готов (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Переустановить"; }
|
||||
else { lblXray.Text = "встроен (" + Core.XrayVersion + ")"; lblXray.ForeColor = OkC; btnXray.Text = "Распаковать"; }
|
||||
}
|
||||
|
||||
// ---- 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 = !switchOn && !busy; }
|
||||
|
||||
Config CaptureConfig()
|
||||
{
|
||||
var c = Core.LoadConfig();
|
||||
c.Mode = Mode; c.FailClosed = chkFail.Checked;
|
||||
if (Mode == "vless") c.Vless = 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 ep;
|
||||
if (Mode == "vless") ok = Vless.ServerReachable(txtField.Text, out ep);
|
||||
else { string h; int p; if (Core.TryParseProxy(txtField.Text, out h, out p)) { ep = h + ":" + p; ok = Core.TcpReachable(h, p, 1500); } else { ep = "некорректный адрес"; ok = false; } }
|
||||
rDot.ForeColor = ok ? OkC : RedC;
|
||||
lblReach.Text = (ok ? "сервер доступен " : "не отвечает ") + ep;
|
||||
}
|
||||
if (Mode == "vless") UpdateXrayStatus();
|
||||
}
|
||||
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
|
||||
{
|
||||
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 proxyArg = Core.ProxyArg(cfg.Proxy);
|
||||
|
||||
if (cfg.FailClosed)
|
||||
if (!Core.ElevateSelf("fwapply")) { if (cfg.Mode == "vless") Core.StopXray(); 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();
|
||||
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 { 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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user