39b58e1a77
Third mode alongside proxy and VLESS+Reality. Bundles wireproxy-awg built from a pinned, audited source commit (fork of wireproxy over the official amneziawg-go), exposing a local SOCKS5 tunnel with no TUN/driver/admin. User picks their AmneziaWG .conf; the app appends a [Socks5] section and routes Claude through it with the same fail-closed firewall. build-wireproxy.ps1 reproducibly builds the binary (portable Go + pinned commit); it is embedded as a resource and not stored in git. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
442 lines
26 KiB
C#
442 lines
26 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|