""" Proxy Chain Manager — GUI Tabs: Live | Chain Builder | Settings """ from __future__ import annotations import logging import queue import sys from typing import Any import customtkinter as ctk from .config import ( LISTEN_HOST, OBFUSCATION_LABELS, OBFUSCATION_MODES, Settings, load_settings, save_settings, ) from .firewall import disengage as fw_disengage, is_admin, is_engaged as fw_is_engaged, request_admin_relaunch from .paths import app_data_dir from .service import ChainService from .sysproxy import clear_system_proxy, is_system_proxy_set from .tray import TrayIcon from .windows_task import install_logon_task, task_exists, uninstall_logon_task LOG_PATH = app_data_dir() / "proxy_chain_manager.log" logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", handlers=[ logging.FileHandler(LOG_PATH, encoding="utf-8"), logging.StreamHandler(sys.stdout), ], ) # ── palette ────────────────────────────────────────────────────────────────── BG = "#0d0d1a" CARD = "#12122a" PANEL = "#1a1a3e" ACCENT = "#1e3a8a" ACCENT2 = "#2563eb" GREEN = "#00e676" RED = "#ff1744" YELLOW = "#ffab00" PURPLE = "#bb86fc" BLUE = "#448aff" CYAN = "#00bcd4" DIM = "#4a5568" TEXT = "#e2e8f0" TEXT2 = "#94a3b8" FONT = "Segoe UI" def main() -> None: ctk.set_appearance_mode("dark") ctk.set_default_color_theme("dark-blue") root = ctk.CTk() root.title("Proxy God v2") root.geometry("1060x720") root.minsize(900, 600) root.configure(fg_color=BG) ui_q: queue.Queue[dict[str, Any]] = queue.Queue() svc = ChainService(notify=lambda m: ui_q.put(m)) s = load_settings() # ── tray ───────────────────────────────────────────────────────────────── def _tray_show() -> None: root.after(0, lambda: (root.deiconify(), root.lift())) def _tray_quit() -> None: root.after(0, _close) tray = TrayIcon(on_show=_tray_show, on_quit=_tray_quit, on_rotate=svc.rotate_now) tray.start() # ───────────────────────────────────────────────────────────────────────── # HELPERS # ───────────────────────────────────────────────────────────────────────── def _btn(parent: Any, label: str, cmd: Any, w: int = 80, h: int = 28, **kw: Any) -> ctk.CTkButton: cfg = {"fg_color": ACCENT, "hover_color": ACCENT2, "text_color": TEXT} cfg.update(kw) return ctk.CTkButton( parent, text=label, command=cmd, width=w, height=h, font=(FONT, 11), corner_radius=6, **cfg, ) pulse_job: dict[str, Any] = {"id": None} def _cancel_pulse() -> None: jid = pulse_job.get("id") if jid: try: root.after_cancel(jid) except Exception: pass pulse_job["id"] = None def _short(u: str) -> str: u = u.replace("http://", "").replace("socks5://", "s5://").replace("socks4://", "s4://") return u[:28] + "…" if len(u) > 30 else u # ───────────────────────────────────────────────────────────────────────── # TOP BAR # ───────────────────────────────────────────────────────────────────────── topbar = ctk.CTkFrame(root, fg_color=CARD, corner_radius=0, height=46) topbar.pack(fill="x", side="top") topbar.pack_propagate(False) status_dot = ctk.CTkLabel(topbar, text="●", font=(FONT, 20), text_color=RED) status_dot.pack(side="left", padx=(10, 2)) status_txt = ctk.CTkLabel(topbar, text="STOPPED", font=(FONT, 12, "bold"), text_color=TEXT) status_txt.pack(side="left", padx=(0, 10)) def _start() -> None: _save_settings() svc.start() _btn(topbar, "▶ Start", _start, w=90).pack(side="left", padx=2) _btn(topbar, "■ Stop", svc.stop, w=80).pack(side="left", padx=2) _btn(topbar, "↻ Rotate", svc.rotate_now, w=80).pack(side="left", padx=2) _btn(topbar, "Quit", lambda: root.after(0, _close), w=55, fg_color="#7f1d1d", hover_color="#991b1b").pack(side="left", padx=(8, 2)) ctk.CTkLabel(topbar, text="│", text_color=DIM).pack(side="left", padx=6) boot_lbl = ctk.CTkLabel(topbar, text="Boot:?", font=(FONT, 10), text_color=TEXT2) boot_lbl.pack(side="left", padx=2) def _refresh_boot() -> None: on = task_exists() boot_lbl.configure(text="Boot: ON" if on else "Boot: OFF", text_color=GREEN if on else DIM) def _inst_boot() -> None: ok, msg = install_logon_task() _log("Boot task installed." if ok else f"Boot install fail: {msg}") _refresh_boot() def _rm_boot() -> None: ok, msg = uninstall_logon_task() _log("Boot task removed." if ok else f"Boot remove fail: {msg}") _refresh_boot() _btn(topbar, "Boot+", _inst_boot, w=55).pack(side="left", padx=2) _btn(topbar, "Boot-", _rm_boot, w=55).pack(side="left", padx=2) # Right side status indicators proxy_lbl = ctk.CTkLabel(topbar, text=f"proxy: {LISTEN_HOST}:{s.local_port}", font=(FONT, 11, "bold"), text_color=BLUE) proxy_lbl.pack(side="right", padx=(4, 12)) fw_lbl = ctk.CTkLabel(topbar, text="FW:—", font=(FONT, 10), text_color=DIM) fw_lbl.pack(side="right", padx=4) sys_lbl = ctk.CTkLabel(topbar, text="SYS:—", font=(FONT, 10), text_color=DIM) sys_lbl.pack(side="right", padx=4) admin_lbl = ctk.CTkLabel(topbar, text="⚡ADMIN" if is_admin() else "👤USER", font=(FONT, 10, "bold"), text_color=GREEN if is_admin() else YELLOW) admin_lbl.pack(side="right", padx=4) if not is_admin(): def _elevate() -> None: if request_admin_relaunch(): _close() _btn(topbar, "Run as Admin", _elevate, w=100, fg_color="#7f1d1d", hover_color="#991b1b").pack(side="right", padx=4) # ───────────────────────────────────────────────────────────────────────── # CHAIN VIZ BAR # ───────────────────────────────────────────────────────────────────────── viz_bar = ctk.CTkFrame(root, fg_color=PANEL, corner_radius=0, height=54) viz_bar.pack(fill="x") viz_bar.pack_propagate(False) your_ip_lbl = ctk.CTkLabel(viz_bar, text="Your IP: —", font=(FONT, 10), text_color=TEXT2) your_ip_lbl.pack(side="left", padx=(12, 6), pady=4) chain_canvas = ctk.CTkFrame(viz_bar, fg_color="transparent") chain_canvas.pack(side="left", fill="both", expand=True, padx=4) exit_ip_lbl = ctk.CTkLabel(viz_bar, text="Exit: —", font=(FONT, 13, "bold"), text_color=YELLOW) exit_ip_lbl.pack(side="right", padx=(6, 14)) hop_dot_labels: list[ctk.CTkLabel] = [] def _pulse(step: int = 0) -> None: if not hop_dot_labels: pulse_job["id"] = None return c = YELLOW if step % 2 == 0 else DIM for d in hop_dot_labels: d.configure(text_color=c) pulse_job["id"] = root.after(380, lambda: _pulse(step + 1)) def _render_chain(hops: list[str], status: str, exit_ip: str | None) -> None: _cancel_pulse() for w in list(chain_canvas.winfo_children()): w.destroy() hop_dot_labels.clear() color_map = {"healthy": GREEN, "dead": RED, "connecting": YELLOW} hop_c = color_map.get(status, DIM) def _node(text: str, color: str, bold: bool = False) -> None: f = (FONT, 11, "bold") if bold else (FONT, 10) ctk.CTkLabel(chain_canvas, text=text, font=f, text_color=color).pack(side="left", padx=1) def _arr() -> None: ctk.CTkLabel(chain_canvas, text="→", font=(FONT, 11), text_color=DIM).pack(side="left", padx=2) _node("YOU", GREEN, bold=True) _arr() _node("NORD", PURPLE, bold=True) _arr() for i, h in enumerate(hops): dot = ctk.CTkLabel(chain_canvas, text="●", font=(FONT, 12), text_color=hop_c) dot.pack(side="left", padx=1) hop_dot_labels.append(dot) ctk.CTkLabel(chain_canvas, text=_short(h), font=(FONT, 10), text_color=hop_c).pack(side="left", padx=1) if i < len(hops) - 1: _arr() _arr() _node("WEB", BLUE, bold=True) if exit_ip: exit_ip_lbl.configure( text=f"Exit: {exit_ip}", text_color=GREEN if status == "healthy" else RED, ) else: exit_ip_lbl.configure(text="Exit: —", text_color=YELLOW if status == "connecting" else DIM) if status == "connecting" and hop_dot_labels: _pulse() # ───────────────────────────────────────────────────────────────────────── # PROGRESS STRIP # ───────────────────────────────────────────────────────────────────────── prog_strip = ctk.CTkFrame(root, fg_color=BG, height=22) prog_strip.pack(fill="x", padx=8, pady=(4, 0)) phase_lbl = ctk.CTkLabel(prog_strip, text="idle", font=(FONT, 10), text_color=TEXT2) phase_lbl.pack(side="left", padx=4) prog_bar = ctk.CTkProgressBar(prog_strip, height=8, corner_radius=4, fg_color=CARD, progress_color=BLUE) prog_bar.pack(side="left", fill="x", expand=True, padx=4) prog_bar.set(0) pool_lbl = ctk.CTkLabel(prog_strip, text="pool: 0", font=(FONT, 10), text_color=TEXT2) pool_lbl.pack(side="right", padx=4) # ───────────────────────────────────────────────────────────────────────── # TABVIEW # ───────────────────────────────────────────────────────────────────────── tabs = ctk.CTkTabview(root, fg_color=BG, segmented_button_fg_color=CARD, segmented_button_selected_color=ACCENT2, segmented_button_unselected_color=CARD, segmented_button_selected_hover_color=ACCENT, border_width=0) tabs.pack(fill="both", expand=True, padx=8, pady=(4, 8)) tab_live = tabs.add(" Live ") tab_chain = tabs.add(" Chain Builder ") tab_settings = tabs.add(" Settings ") # ─── TAB: LIVE ─────────────────────────────────────────────────────────── tab_live.configure(fg_color=BG) # Stats row stats_row = ctk.CTkFrame(tab_live, fg_color="transparent") stats_row.pack(fill="x", pady=(4, 6)) def _stat_card(parent: Any, title: str, var: ctk.StringVar) -> None: f = ctk.CTkFrame(parent, fg_color=CARD, corner_radius=8) f.pack(side="left", fill="x", expand=True, padx=4) ctk.CTkLabel(f, text=title, font=(FONT, 9), text_color=TEXT2).pack(anchor="w", padx=8, pady=(6, 0)) ctk.CTkLabel(f, textvariable=var, font=(FONT, 15, "bold"), text_color=TEXT).pack(anchor="w", padx=8, pady=(0, 6)) v_real_ip = ctk.StringVar(value="—") v_exit_ip = ctk.StringVar(value="—") v_pool = ctk.StringVar(value="0") v_hops = ctk.StringVar(value="—") _stat_card(stats_row, "Your IP (Nord/ISP)", v_real_ip) _stat_card(stats_row, "Exit IP (last proxy)", v_exit_ip) _stat_card(stats_row, "Valid proxy pool", v_pool) _stat_card(stats_row, "Active hops", v_hops) log_frame = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8) log_frame.pack(fill="both", expand=True, padx=0) log_box = ctk.CTkTextbox(log_frame, font=("Consolas", 11), fg_color=BG, text_color=TEXT, scrollbar_button_color=ACCENT) log_box.pack(fill="both", expand=True, padx=4, pady=4) def _log(line: str) -> None: log_box.insert("end", line + "\n") log_box.see("end") # ─── TAB: CHAIN BUILDER ────────────────────────────────────────────────── tab_chain.configure(fg_color=BG) cb_top = ctk.CTkFrame(tab_chain, fg_color="transparent") cb_top.pack(fill="x", pady=(4, 8)) # Left col — mode + hops cb_left = ctk.CTkFrame(cb_top, fg_color=CARD, corner_radius=8) cb_left.pack(side="left", fill="both", expand=True, padx=(0, 6)) ctk.CTkLabel(cb_left, text="Obfuscation Mode", font=(FONT, 12, "bold"), text_color=TEXT).pack( anchor="w", padx=10, pady=(10, 4)) mode_var = ctk.StringVar(value=s.obfuscation_mode) for mode_key in OBFUSCATION_MODES: ctk.CTkRadioButton( cb_left, text=OBFUSCATION_LABELS[mode_key], variable=mode_var, value=mode_key, font=(FONT, 11), fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT, ).pack(anchor="w", padx=14, pady=2) ctk.CTkLabel(cb_left, text="", height=6).pack() ctk.CTkLabel(cb_left, text="Hop Count", font=(FONT, 12, "bold"), text_color=TEXT).pack( anchor="w", padx=10) hop_val_lbl = ctk.CTkLabel(cb_left, text=str(s.chain_length), font=(FONT, 22, "bold"), text_color=CYAN) hop_val_lbl.pack(pady=(2, 0)) def _on_hop_slider(val: float) -> None: hop_val_lbl.configure(text=str(int(val))) hop_slider = ctk.CTkSlider(cb_left, from_=2, to=8, number_of_steps=6, command=_on_hop_slider, fg_color=CARD, progress_color=ACCENT2, button_color=CYAN, button_hover_color=BLUE) hop_slider.set(s.chain_length) hop_slider.pack(fill="x", padx=14, pady=(2, 12)) elite_var2 = ctk.BooleanVar(value=s.prefer_elite) ctk.CTkCheckBox(cb_left, text="Elite proxies only", variable=elite_var2, font=(FONT, 11), fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT).pack( anchor="w", padx=14, pady=(0, 10)) # Right col — manual chain editor cb_right = ctk.CTkFrame(cb_top, fg_color=CARD, corner_radius=8, width=380) cb_right.pack(side="left", fill="both", expand=True) hdr_row = ctk.CTkFrame(cb_right, fg_color="transparent") hdr_row.pack(fill="x", padx=10, pady=(10, 4)) ctk.CTkLabel(hdr_row, text="Manual Chain", font=(FONT, 12, "bold"), text_color=TEXT).pack(side="left") use_manual_var = ctk.BooleanVar(value=s.use_pinned_chain) ctk.CTkCheckBox(hdr_row, text="Use this chain", variable=use_manual_var, font=(FONT, 10), fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT2).pack(side="right") # Listbox-style chain using a scrollable frame with rows chain_list_frame = ctk.CTkScrollableFrame(cb_right, fg_color=BG, height=160, corner_radius=6, scrollbar_button_color=ACCENT) chain_list_frame.pack(fill="x", padx=10, pady=(0, 6)) manual_chain: list[str] = list(s.pinned_chain) row_frames: list[ctk.CTkFrame] = [] def _rebuild_chain_ui() -> None: for w in list(chain_list_frame.winfo_children()): w.destroy() row_frames.clear() for i, hop in enumerate(manual_chain): _chain_row(i, hop) def _chain_row(idx: int, hop: str) -> None: fr = ctk.CTkFrame(chain_list_frame, fg_color=PANEL, corner_radius=6, height=30) fr.pack(fill="x", pady=2) fr.pack_propagate(False) row_frames.append(fr) # Drag indicator + index ctk.CTkLabel(fr, text=f" {idx+1}.", font=(FONT, 10, "bold"), text_color=CYAN, width=24).pack(side="left") # Protocol badge proto = hop.split("://")[0].upper() if "://" in hop else "?" badge_c = {"HTTP": "#1e3a8a", "SOCKS5": "#064e3b", "SOCKS4": "#3b1a64"}.get(proto, DIM) ctk.CTkLabel(fr, text=proto, font=(FONT, 9, "bold"), fg_color=badge_c, corner_radius=4, padx=4, text_color=TEXT).pack(side="left", padx=2) ctk.CTkLabel(fr, text=_short(hop), font=("Consolas", 10), text_color=TEXT, anchor="w").pack(side="left", fill="x", expand=True, padx=4) def _up(i: int = idx) -> None: if i > 0: manual_chain[i], manual_chain[i-1] = manual_chain[i-1], manual_chain[i] _rebuild_chain_ui() def _dn(i: int = idx) -> None: if i < len(manual_chain) - 1: manual_chain[i], manual_chain[i+1] = manual_chain[i+1], manual_chain[i] _rebuild_chain_ui() def _rm(i: int = idx) -> None: del manual_chain[i] _rebuild_chain_ui() _btn(fr, "↑", _up, w=24, h=22, fg_color=DIM, hover_color=ACCENT).pack(side="right", padx=1) _btn(fr, "↓", _dn, w=24, h=22, fg_color=DIM, hover_color=ACCENT).pack(side="right", padx=1) _btn(fr, "✕", _rm, w=24, h=22, fg_color="#7f1d1d", hover_color=RED).pack(side="right", padx=(1, 4)) _rebuild_chain_ui() # Add proxy row add_row = ctk.CTkFrame(cb_right, fg_color="transparent") add_row.pack(fill="x", padx=10, pady=(0, 4)) add_entry = ctk.CTkEntry(add_row, placeholder_text="http://1.2.3.4:8080 or socks5://...", font=(FONT, 10), fg_color=BG, border_color=ACCENT, height=28) add_entry.pack(side="left", fill="x", expand=True, padx=(0, 4)) def _add_proxy() -> None: v = add_entry.get().strip() if v and ("://" in v or ":" in v): if "://" not in v: v = "http://" + v manual_chain.append(v) _rebuild_chain_ui() add_entry.delete(0, "end") else: _log("Invalid proxy format — use http://ip:port or socks5://ip:port") _btn(add_row, "+ Add", _add_proxy, w=70, h=28).pack(side="left") def _paste_from_current() -> None: for h in svc.current_chain: if h not in manual_chain: manual_chain.append(h) _rebuild_chain_ui() _log("Pasted active chain into manual chain editor.") _btn(cb_right, "↙ Paste current chain", _paste_from_current, w=200, h=26, fg_color=DIM, hover_color=ACCENT).pack(pady=(0, 10)) # Sources section src_frame = ctk.CTkFrame(tab_chain, fg_color=CARD, corner_radius=8) src_frame.pack(fill="x", pady=(0, 6)) src_hdr = ctk.CTkFrame(src_frame, fg_color="transparent") src_hdr.pack(fill="x", padx=10, pady=(8, 4)) ctk.CTkLabel(src_hdr, text="Proxy Sources (one URL per line)", font=(FONT, 12, "bold"), text_color=TEXT).pack(side="left") sources_box = ctk.CTkTextbox(src_frame, height=80, font=("Consolas", 10), fg_color=BG, text_color=TEXT, scrollbar_button_color=ACCENT) sources_box.pack(fill="x", padx=10, pady=(0, 8)) sources_box.insert("end", "\n".join(s.sources)) # ─── TAB: SETTINGS ─────────────────────────────────────────────────────── tab_settings.configure(fg_color=BG) sf_outer = ctk.CTkScrollableFrame(tab_settings, fg_color=BG, scrollbar_button_color=ACCENT) sf_outer.pack(fill="both", expand=True) def _section(title: str) -> ctk.CTkFrame: ctk.CTkLabel(sf_outer, text=title, font=(FONT, 12, "bold"), text_color=CYAN).pack( anchor="w", padx=6, pady=(14, 4)) f = ctk.CTkFrame(sf_outer, fg_color=CARD, corner_radius=8) f.pack(fill="x", padx=6, pady=(0, 4)) return f entries: dict[str, ctk.CTkEntry] = {} def _fld(parent: Any, label: str, key: str, val: str, tip: str = "") -> None: row = ctk.CTkFrame(parent, fg_color="transparent") row.pack(fill="x", padx=10, pady=4) ctk.CTkLabel(row, text=label, font=(FONT, 11), text_color=TEXT, width=220, anchor="w").pack(side="left") e = ctk.CTkEntry(row, height=26, font=(FONT, 11), fg_color=BG, border_color=ACCENT, width=120) e.insert(0, val) e.pack(side="left") if tip: ctk.CTkLabel(row, text=f" {tip}", font=(FONT, 10), text_color=TEXT2).pack(side="left") entries[key] = e net = _section("Network") _fld(net, "Local port", "port", str(s.local_port), "(apps point here)") _fld(net, "Proxy bypass list", "bypass", s.proxy_bypass, "(semicolon separated)") _fld(net, "IP check URL", "check_url", s.ip_check_url) timing = _section("Timing") _fld(timing, "Health check interval (sec)", "health", str(s.health_check_seconds), "(0 = manual only)") _fld(timing, "Full list refresh (sec)", "refresh", str(s.full_refresh_seconds)) validation = _section("Validation") _fld(validation, "Concurrent proxy tests", "conc", str(s.validation_concurrency)) _fld(validation, "Max candidates per cycle", "maxc", str(s.max_candidates)) _fld(validation, "Per-proxy timeout (sec)", "timeout", str(s.validation_timeout_seconds)) security = _section("Security") ks_row = ctk.CTkFrame(security, fg_color="transparent") ks_row.pack(fill="x", padx=10, pady=6) ks_var = ctk.BooleanVar(value=s.kill_switch_enabled) ctk.CTkCheckBox(ks_row, text="Firewall kill-switch (block all traffic if proxy chain dies)", variable=ks_var, font=(FONT, 11), fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT).pack(side="left") save_btn_frame = ctk.CTkFrame(sf_outer, fg_color="transparent") save_btn_frame.pack(fill="x", padx=6, pady=8) _btn(save_btn_frame, "💾 Save All Settings", lambda: _save_settings(verbose=True), w=220, h=36, font=(FONT, 13)).pack(side="left") # ───────────────────────────────────────────────────────────────────────── # SAVE SETTINGS # ───────────────────────────────────────────────────────────────────────── def _save_settings(verbose: bool = False) -> None: try: src_raw = sources_box.get("1.0", "end").strip() src_list = [ln.strip() for ln in src_raw.splitlines() if ln.strip().startswith("http")] ns = Settings( local_host=LISTEN_HOST, local_port=int(entries["port"].get().strip()), chain_length=int(hop_slider.get()), obfuscation_mode=mode_var.get(), use_pinned_chain=bool(use_manual_var.get()), pinned_chain=list(manual_chain), health_check_seconds=int(entries["health"].get().strip()), full_refresh_seconds=int(entries["refresh"].get().strip()), validation_concurrency=int(entries["conc"].get().strip()), max_candidates=int(entries["maxc"].get().strip()), validation_timeout_seconds=float(entries["timeout"].get().strip()), prefer_elite=bool(elite_var2.get()), kill_switch_enabled=bool(ks_var.get()), proxy_bypass=entries["bypass"].get().strip() or Settings().proxy_bypass, sources=src_list or Settings().sources, ip_check_url=entries["check_url"].get().strip() or Settings().ip_check_url, ) if not (1 <= ns.local_port <= 65535): raise ValueError("Port must be 1-65535") svc.update_settings(ns) save_settings(ns) proxy_lbl.configure(text=f"proxy: {LISTEN_HOST}:{ns.local_port}") if verbose: _log("✓ Settings saved.") except Exception as e: _log(f"Settings error: {e!s}") # ───────────────────────────────────────────────────────────────────────── # STATUS REFRESHERS # ───────────────────────────────────────────────────────────────────────── def _refresh_sysproxy() -> None: on = is_system_proxy_set() sys_lbl.configure(text="SYS:ON" if on else "SYS:OFF", text_color=GREEN if on else DIM) def _refresh_fw(engaged: bool | None = None) -> None: if engaged is None: engaged = fw_is_engaged() fw_lbl.configure(text="FW:ON" if engaged else "FW:OFF", text_color=GREEN if engaged else DIM) # ───────────────────────────────────────────────────────────────────────── # MESSAGE PUMP # ───────────────────────────────────────────────────────────────────────── def _handle(m: dict[str, Any]) -> None: t = m.get("type") if t == "log": _log(str(m.get("text", ""))) elif t == "state": running = bool(m.get("running")) status_dot.configure(text_color=GREEN if running else RED) status_txt.configure(text="RUNNING" if running else "STOPPED") _refresh_sysproxy() if not running: prog_bar.set(0) phase_lbl.configure(text="idle") tray.set_state("gray") v_exit_ip.set("—") v_hops.set("—") elif t == "phase": phase_lbl.configure(text=str(m.get("phase", ""))) if m.get("phase") != "validate": prog_bar.set(0) elif t == "validate_progress": d, tot = int(m.get("done", 0)), max(1, int(m.get("total", 1))) prog_bar.set(d / tot) elif t == "pool": cnt = int(m.get("count", 0)) pool_lbl.configure(text=f"pool: {cnt}") v_pool.set(str(cnt)) elif t == "real_ip": ip = str(m.get("ip", "—")) your_ip_lbl.configure(text=f"Your IP: {ip}") v_real_ip.set(ip) elif t == "firewall": _refresh_fw(m.get("engaged")) elif t == "hops": hops = [str(x) for x in (m.get("hops") or [])] status = str(m.get("status", "connecting")) exit_ip = m.get("exit_ip") _render_chain(hops, status, exit_ip) _refresh_sysproxy() tray.set_state({"healthy": "green", "dead": "red", "connecting": "yellow"}.get(status, "yellow")) v_exit_ip.set(str(exit_ip) if exit_ip else "—") v_hops.set(str(len(hops))) def _pump() -> None: try: while True: _handle(ui_q.get_nowait()) except queue.Empty: pass root.after(100, _pump) # ───────────────────────────────────────────────────────────────────────── # CLOSE / MINIMIZE # ───────────────────────────────────────────────────────────────────────── def _close() -> None: _cancel_pulse() tray.stop() svc.stop() clear_system_proxy() if is_admin(): fw_disengage() root.destroy() root.protocol("WM_DELETE_WINDOW", lambda: root.withdraw()) # ───────────────────────────────────────────────────────────────────────── # INIT # ───────────────────────────────────────────────────────────────────────── _refresh_boot() _refresh_sysproxy() _refresh_fw() _log("─" * 60) _log("Proxy God v2 — ready.") _log(f"Admin: {'YES' if is_admin() else 'NO (Run as Admin for kill-switch)'}") _log(f"Log: {LOG_PATH}") _log("Press START to fetch, validate and chain proxies.") _log("─" * 60) _pump() root.mainloop() if __name__ == "__main__": main()