From 36becdce0607984eaf0e77965a06676d06fa62e5 Mon Sep 17 00:00:00 2001 From: xVRVx <63975481+xVRVx@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:02:58 +0300 Subject: [PATCH] Delete winLoadXRAY.py --- winLoadXRAY.py | 1069 ------------------------------------------------ 1 file changed, 1069 deletions(-) delete mode 100644 winLoadXRAY.py diff --git a/winLoadXRAY.py b/winLoadXRAY.py deleted file mode 100644 index c95611f..0000000 --- a/winLoadXRAY.py +++ /dev/null @@ -1,1069 +0,0 @@ -import tkinter as tk -from tkinter import messagebox -import customtkinter as ctk -from PIL import Image, ImageTk -import base64 -import requests -import json -import sys -import os -import shutil -import subprocess -import winreg -import re -import ctypes -import webbrowser -import threading -import time -import random -import concurrent.futures -import urllib.parse -from urllib.parse import urlparse, parse_qs, unquote -import socket - -# Пытаемся подключить библиотеку для системного трея -try: - import pystray - from pystray import MenuItem as tray_item - HAS_TRAY = True -except ImportError: - HAS_TRAY = False - -sys.path.append(os.path.join(os.path.dirname(__file__), 'func')) -from parsing import parse_vless, parse_shadowsocks -from configXray import generate_config -from tun2proxy import get_default_interface, patch_direct_out_interface, start_tun2proxy, stop_tun2proxy -from copyPast import cmd_copy, cmd_cut, cmd_select_all - -ctk.set_appearance_mode("dark") - -APP_NAME = "winLoadXRAY" -APP_VERS = "v1.07-beta" -XRAY_VERS = "v26.2.6" - -xray_process = None -tun_enabled = False -IS_AUTOSTART = "--autostart" in sys.argv - -# --- IPC для обработки ссылок winloadxray:// и WAKEUP --- -IPC_PORT = 20810 - -def get_url_from_args(): - for arg in sys.argv: - if arg.lower().startswith("winloadxray://add/"): - return arg[18:] # Отрезаем префикс - return None - -def send_url_to_existing_instance(url): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(1) - s.connect(("127.0.0.1", IPC_PORT)) - s.sendall(url.encode('utf-8')) - return True - except: - return False - -# Сразу перехватываем ссылку до инициализации графики -startup_url = get_url_from_args() -msg = startup_url if startup_url else "WAKEUP" -if send_url_to_existing_instance(msg): - # Если программа уже открыта, отправляем ей сигнал по сокету и тихо закрываем этот экземпляр - sys.exit(0) - -BASE_APP_DIR = os.path.join(os.getenv('APPDATA'), APP_NAME) -PROFILES_DIR = os.path.join(BASE_APP_DIR, 'profiles') -os.makedirs(PROFILES_DIR, exist_ok=True) - -CONFIGS_DIR = "" -LINKS_FILE = "" -STATE_FILE = "" -active_profile = "Default" - -active_tag = None -proxy_enabled = False -base64_urls =[] -configs = {} - -# --- Инструменты --- -def sanitize_filename(name): - return re.sub(r'[<>:"/\\|?*]', '_', name).strip() - -def safe_b64decode(s): - s = s.strip() - return base64.b64decode(s + '=' * (-len(s) % 4)).decode('utf-8', errors='ignore') - -def split_flag(tag): - if len(tag) >= 2 and '\U0001F1E6' <= tag[0] <= '\U0001F1FF' and '\U0001F1E6' <= tag[1] <= '\U0001F1FF': - return tag[:2], tag[2:].strip() - if len(tag) >= 1 and ord(tag[0]) > 0x2500 and not (0x4E00 <= ord(tag[0]) <= 0x9FFF): - return tag[0], tag[1:].strip() - return "", tag - -# Определение типа конфига (vless raw, ss, XRAY и т.д.) -def get_config_type(data): - try: - if "outbounds" not in data: - proto = data.get("protocol", "") - if proto == "vless": - net = data.get("network", "raw") - return f"vless {net}" - elif proto == "shadowsocks": - return "ss" - - outbounds = data.get("outbounds",[]) - if not outbounds: return "XRAY" - if len(outbounds) > 4: return "XRAY" - - proto = outbounds[0].get("protocol", "") - if proto == "vless": - net = outbounds[0].get("streamSettings", {}).get("network", "raw") - return f"vless {net}" - elif proto == "shadowsocks": - return "ss" - else: - return "XRAY" - except: - return "XRAY" - -def get_sni_from_config(file_path: str) -> str or None: - try: - with open(file_path, 'r', encoding='utf-8') as f: - config = json.load(f) - if not ("outbounds" in config and len(config['outbounds']) > 0): return None - stream_settings = config['outbounds'][0].get('streamSettings', {}) - if not stream_settings: return None - if 'realitySettings' in stream_settings and stream_settings['realitySettings'].get('serverName'): - return stream_settings['realitySettings']['serverName'] - if 'tlsSettings' in stream_settings and stream_settings['tlsSettings'].get('serverName'): - return stream_settings['tlsSettings']['serverName'] - except: return None - return None - -def http_ping(hostname: str, timeout: int = 3) -> (int, str): - if not hostname: return -1, "No SNI" - url = f"https://{hostname}" - proxies = {"http": None, "https": None} - try: - start_time = time.perf_counter() - response = requests.head(url, timeout=timeout, proxies=proxies) - end_time = time.perf_counter() - if 200 <= response.status_code < 400: - return round((end_time - start_time) * 1000), "OK" - return -1, f"HTTP {response.status_code}" - except: return -1, "Error" - -# --- Профили и Файлы --- -def setup_active_profile(profile_name): - global CONFIGS_DIR, LINKS_FILE, STATE_FILE, active_profile, configs, base64_urls - active_profile = profile_name - CONFIGS_DIR = os.path.join(PROFILES_DIR, profile_name) - os.makedirs(CONFIGS_DIR, exist_ok=True) - LINKS_FILE = os.path.join(CONFIGS_DIR, "links.json") - STATE_FILE = os.path.join(CONFIGS_DIR, "state.json") - configs.clear() - base64_urls =[] - - settings_path = os.path.join(BASE_APP_DIR, "settings.json") - try: - with open(settings_path, "w", encoding="utf-8") as f: - json.dump({"active_profile": active_profile}, f) - except: pass - -def get_profiles(): - profs =[d for d in os.listdir(PROFILES_DIR) if os.path.isdir(os.path.join(PROFILES_DIR, d))] - return profs if profs else["Default"] - -def load_initial_profile(): - settings_path = os.path.join(BASE_APP_DIR, "settings.json") - prof = "Default" - if os.path.exists(settings_path): - try: - with open(settings_path, "r", encoding="utf-8") as f: - prof = json.load(f).get("active_profile", "Default") - except: pass - if not os.path.exists(os.path.join(PROFILES_DIR, prof)): prof = "Default" - setup_active_profile(prof) - -def save_state(): - state = {"active_tag": active_tag, "proxy_enabled": proxy_enabled} - try: - with open(STATE_FILE, "w", encoding="utf-8") as f: - json.dump(state, f, indent=2) - except: pass - -def load_state(is_initial=False): - global active_tag, proxy_enabled - if not os.path.exists(STATE_FILE): return - try: - with open(STATE_FILE, "r", encoding="utf-8") as f: - state = json.load(f) - active_tag = state.get("active_tag") - proxy_enabled = state.get("proxy_enabled", False) - - if proxy_enabled: - toggle_system_proxy() - toggle_system_proxy() - - if active_tag and active_tag in configs: - config_list.select(active_tag) - if is_initial: - config_path = os.path.join(CONFIGS_DIR, f"{active_tag}.json") - if os.path.exists(config_path): - global xray_process - xray_process = subprocess.Popen([XRAY_EXE, "-config", config_path], creationflags=CREATE_NO_WINDOW) - btn_run.configure(text="Остановить конфиг", fg_color="#27AE60", hover_color="#2ECC71") - except: pass - -def load_base64_urls(): - configs.clear() - config_list.clear() - for filename in os.listdir(CONFIGS_DIR): - if filename.endswith(".json") and filename not in ("links.json", "state.json"): - try: - with open(os.path.join(CONFIGS_DIR, filename), "r", encoding="utf-8") as f: - config_data = json.load(f) - tag = config_data.get("tag", os.path.splitext(filename)[0]) - configs[tag] = config_data - ctype = get_config_type(config_data) - config_list.insert(tag, ctype) - except: pass - - global base64_urls - if os.path.exists(LINKS_FILE): - with open(LINKS_FILE, "r", encoding="utf-8") as f: - links = json.load(f) - base64_urls = links if isinstance(links, list) else[] - else: - base64_urls =[] - - entry.delete(0, 'end') - if base64_urls: - entry.insert(0, base64_urls[0]) - -def save_base64_urls(): - with open(LINKS_FILE, "w", encoding="utf-8") as f: - json.dump(base64_urls, f, ensure_ascii=False, indent=2) - -def clear_xray_configs(): - configs.clear() - config_list.clear() - for filename in os.listdir(CONFIGS_DIR): - if filename.endswith(".json") and filename not in ("links.json", "state.json"): - try: os.remove(os.path.join(CONFIGS_DIR, filename)) - except: pass - -# --- Основные функции --- -def toggle_system_proxy(host="127.0.0.1", port=2080): - global proxy_enabled - path = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings" - try: - with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_SET_VALUE) as key: - if not proxy_enabled: - winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1) - winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, f"{host}:{port}") - proxy_enabled = True - else: - winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0) - proxy_enabled = False - save_state() - update_proxy_button_color() - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось переключить прокси: {e}") - -def stop_system_proxy(is_quitting=False): - global proxy_enabled - try: - path = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings" - with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0, winreg.KEY_SET_VALUE) as key: - winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0) - if not is_quitting: - proxy_enabled = False - update_proxy_button_color() - save_state() - except: pass - -def update_proxy_button_color(): - if proxy_enabled: - btn_proxy.configure(text="Выключить системный прокси", fg_color="#D35400", hover_color="#A04000") - else: - btn_proxy.configure(text="Включить системный прокси", fg_color=["#3B8ED0", "#1F6AA5"], hover_color=["#36719F", "#144870"]) - -def paste_and_add(): - try: - clip = root.clipboard_get() - entry.delete(0, 'end') - entry.insert(0, clip) - add_from_url() - except: pass - -def add_from_url(is_refresh=False): - global base64_urls, active_profile, tun_enabled - input_text = entry.get().strip() - if not input_text: return - - # Принудительно останавливаем все сетевые процессы при обновлении подписки - stop_xray() - stop_system_proxy() - if tun_enabled: - vrv_tun_mode_toggle() - - if input_text.startswith("vless://") or input_text.startswith("ss://"): - lines =[l.strip() for l in input_text.splitlines() if l.strip()] - added = 0 - for line in lines: - try: - if line.startswith("vless://"): data = parse_vless(line) - elif line.startswith("ss://"): data = parse_shadowsocks(line) - else: continue - tag = data["tag"] - configs[tag] = data - ctype = get_config_type(data) - config_list.insert(tag, ctype) - with open(os.path.join(CONFIGS_DIR, f"{tag}.json"), "w", encoding="utf-8") as f: - f.write(generate_config(data)) - added += 1 - except: pass - if added > 0 and not is_refresh: - messagebox.showinfo("Добавлено", f"Добавлено конфигов в текущий профиль: {added}") - return - - if input_text.startswith("http"): - try: - r = requests.get(input_text, headers={'User-Agent': f'{APP_NAME}/{APP_VERS}'}) - r.raise_for_status() - - if not is_refresh: - prof_name = None - title_header = r.headers.get('profile-title') - if title_header: - if title_header.startswith('base64:'): - try: - prof_name = safe_b64decode(title_header.split('base64:')[1]) - except: pass - else: - prof_name = title_header - - if not prof_name: - prof_name = urllib.parse.urlparse(input_text).netloc - - prof_name = sanitize_filename(prof_name) - if not prof_name: prof_name = "Subscription" - - base_prof_name = prof_name - counter = 2 - while True: - prof_dir = os.path.join(PROFILES_DIR, prof_name) - links_file = os.path.join(prof_dir, "links.json") - if os.path.exists(prof_dir): - if os.path.exists(links_file): - try: - with open(links_file, "r", encoding="utf-8") as lf: - existing_links = json.load(lf) - if existing_links and existing_links[0] == input_text: - break - except: pass - prof_name = f"{base_prof_name} ({counter})" - counter += 1 - else: - break - - if prof_name != active_profile: - profs = get_profiles() - if prof_name not in profs: - profs.append(prof_name) - profile_dropdown.configure(values=profs) - setup_active_profile(prof_name) - profile_var.set(prof_name) - - clear_xray_configs() - base64_urls = [input_text] - save_base64_urls() - - added = 0 - try: - decoded = safe_b64decode(r.text) - lines =[l.strip() for l in decoded.splitlines() if l.startswith("vless://") or l.startswith("ss://")] - for line in lines: - try: - data = parse_vless(line) if line.startswith("vless://") else parse_shadowsocks(line) - tag = data["tag"] - configs[tag] = data - ctype = get_config_type(data) - config_list.insert(tag, ctype) - with open(os.path.join(CONFIGS_DIR, f"{tag}.json"), "w", encoding="utf-8") as f: - f.write(generate_config(data)) - added += 1 - except: pass - except: - clean_content = re.sub(r'<[^>]+>', '', r.text).strip() - try: - loaded_data = json.loads(clean_content) - items = loaded_data if isinstance(loaded_data, list) else[loaded_data] - for config_data in items: - tag = sanitize_filename(unquote(config_data.get("remarks", config_data.get("tag", f"import_{added}")))) - configs[tag] = config_data - ctype = get_config_type(config_data) - config_list.insert(tag, ctype) - with open(os.path.join(CONFIGS_DIR, f"{tag}.json"), "w", encoding="utf-8") as cf: - json.dump(config_data, cf, indent=2, ensure_ascii=False) - added += 1 - except: - if not is_refresh: messagebox.showerror("Ошибка", "Не удалось распарсить подписку.") - return - - if not is_refresh: messagebox.showinfo("Успех", f"Подписка добавлена/обновлена ({added} серверов).") - except Exception as e: - if not is_refresh: messagebox.showerror("Ошибка", f"Не удалось загрузить подписку: {e}") - return - if not is_refresh: messagebox.showerror("Ошибка", "Неверная ссылка.") - -def update_all_subscriptions(): - if not base64_urls: - messagebox.showinfo("Внимание", "Нет подписок для обновления в этом профиле.") - return - url = base64_urls[0] - entry.delete(0, 'end') - entry.insert(0, url) - add_from_url(is_refresh=True) - messagebox.showinfo("Успех", "Подписка успешно обновлена!") - -def run_selected(): - global xray_process - - tag = config_list.selected_tag - if not tag: return - - # Проверяем, запущен ли сейчас процесс - if xray_process and xray_process.poll() is None: - # Запоминаем, совпадает ли выбранный конфиг с тем, который сейчас работает - is_same_config = (active_tag == tag) - - # Останавливаем текущий процесс - stop_xray() - - # Если мы нажали на тот же самый конфиг, что и работал, то просто выходим (оставляем выключенным) - if is_same_config: - return - - # А если конфиги разные, код пойдет дальше и сразу запустит новый! - - config_path = os.path.join(CONFIGS_DIR, f"{tag}.json") - if not os.path.exists(XRAY_EXE): - messagebox.showerror("Ошибка", "Файл xray.exe не найден.") - return - - try: - xray_process = subprocess.Popen([XRAY_EXE, "-config", config_path], creationflags=CREATE_NO_WINDOW) - highlight_active(tag) - btn_run.configure(text="Остановить конфиг", fg_color="#27AE60", hover_color="#2ECC71") - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось запустить Xray: {e}") - -def stop_xray(is_quitting=False): - global xray_process - if xray_process and xray_process.poll() is None: - try: - xray_process.terminate() - xray_process.wait() - except: pass - xray_process = None - - if not is_quitting: - clear_highlight() - btn_run.configure(text="Запустить конфиг", fg_color=["#3B8ED0", "#1F6AA5"], hover_color=["#36719F", "#144870"]) - -# --- Управление профилями --- -def switch_profile(new_profile): - global active_profile - if new_profile == active_profile: return - stop_xray() - stop_system_proxy() - setup_active_profile(new_profile) - load_base64_urls() - load_state(is_initial=False) - -def add_new_profile(): - dialog = ctk.CTkInputDialog(text="Имя нового профиля:", title="Создать профиль") - name = dialog.get_input() - if name: - name = sanitize_filename(name.strip()) - if name: - setup_active_profile(name) - profs = get_profiles() - profile_dropdown.configure(values=profs) - profile_var.set(name) - load_base64_urls() - load_state(is_initial=False) - -def delete_current_profile(): - if len(get_profiles()) <= 1: - messagebox.showwarning("Внимание", "Нельзя удалить единственный профиль.") - return - confirm = messagebox.askyesno("Удаление", f"Удалить профиль '{active_profile}' со всеми конфигами?") - if confirm: - stop_xray() - stop_system_proxy() - try: shutil.rmtree(CONFIGS_DIR) - except: pass - profs = get_profiles() - if not profs: profs = ["Default"] - new_prof = profs[0] - profile_dropdown.configure(values=profs) - profile_var.set(new_prof) - switch_profile(new_prof) - -# --- Рандомный автовыбор --- -def on_auto_select_click(): - tags = config_list.get_all_tags() - if not tags: return - btn_auto.configure(state="disabled", text="Ищем...") - - def ping_all_task(): - def check_tag(t): - config_path = os.path.join(CONFIGS_DIR, f"{t}.json") - sni = get_sni_from_config(config_path) - if sni: - ms, _ = http_ping(sni, timeout=2) - return t, ms - return t, -1 - - with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: - results = list(executor.map(check_tag, tags)) - - valid_results =[r for r in results if r[1] >= 0] - best_tag = random.choice(valid_results)[0] if valid_results else None - - def update_ui(): - for original_tag in tags: - ms = next((r[1] for r in results if r[0] == original_tag), -1) - res_str = f"{ms} ms" if ms >= 0 else "Ошибка" - config_list.update_ping(original_tag, res_str) - - if best_tag: - config_list.select(best_tag) - if xray_process and xray_process.poll() is None: stop_xray() - run_selected() - - def finish(): - btn_auto.configure(state="normal", text="Автовыбор") - for original_tag in tags: - config_list.update_ping(original_tag, "") - - root.after(2000, finish) - root.after(0, update_ui) - threading.Thread(target=ping_all_task, daemon=True).start() - -# --- Контекстное меню --- -def on_context_ping_click(): - tag = config_list.selected_tag - if not tag: return - config_path = os.path.join(CONFIGS_DIR, f"{tag}.json") - sni = get_sni_from_config(config_path) - - def ping_task(): - ms, status = http_ping(sni, timeout=2) if sni else (-1, "No SNI") - res_str = f"{ms} ms" if ms >= 0 else ("Ошибка" if sni else "No SNI") - def update_ui(): - config_list.update_ping(tag, res_str) - root.after(2000, lambda: config_list.update_ping(tag, "")) - root.after(0, update_ui) - threading.Thread(target=ping_task, daemon=True).start() - -def on_context_delete_config(): - tag = config_list.selected_tag - if not tag: return - if active_tag == tag: - stop_xray() - stop_system_proxy() - try: os.remove(os.path.join(CONFIGS_DIR, f"{tag}.json")) - except: pass - config_list.delete(tag) - if tag in configs: del configs[tag] - -def show_context_menu(event, tag): - context_menu.tk_popup(event.x_root, event.y_root) - -# --- Доп функции (TUN, Update, Autostart) --- -def restart_xray_with_active(): - global xray_process - if not active_tag: return - config_path = os.path.join(CONFIGS_DIR, f"{active_tag}.json") - if not os.path.exists(config_path): return - try: - xray_process = subprocess.Popen([XRAY_EXE, "-config", config_path], creationflags=CREATE_NO_WINDOW) - highlight_active(active_tag) - btn_run.configure(text="Остановить конфиг", fg_color="#27AE60", hover_color="#2ECC71") - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось перезапустить Xray: {e}") - -def vrv_tun_mode_toggle(): - global tun_enabled, active_tag - if not is_admin(): - run_as_admin() - return - - if not tun_enabled: - interface = get_default_interface() - patch_direct_out_interface(CONFIGS_DIR, interface) - - saved_tag = active_tag - stop_xray() - if saved_tag: - active_tag = saved_tag - restart_xray_with_active() - - stop_tun2proxy() # Очистка процесса перед стартом для надежности - start_tun2proxy(resource_path("tun2proxy/tun2proxy-bin.exe")) - btn_tun.configure(text="Выключить TUN", fg_color="#C0392B", hover_color="#E74C3C") - tun_enabled = True - else: - stop_tun2proxy() - btn_tun.configure(text="Включить TUN", fg_color=["#3B8ED0", "#1F6AA5"], hover_color=["#36719F", "#144870"]) - tun_enabled = False - -def check_latest_version(): - try: - response = requests.get("https://api.github.com/repos/xVRVx/winLoadXRAY/releases/latest", timeout=10) - response.raise_for_status() - latest_version = response.json().get("tag_name", "") - if latest_version and latest_version != APP_VERS: - update_link = ctk.CTkLabel(frame_links, text=f"Доступна: {latest_version}", text_color="#F1C40F", cursor="hand2", font=("Arial", 12, "underline")) - ToolTip(update_link, "Замените: "+ get_executable_path()) - update_link.pack(side="right", padx=0) - update_link.bind("", lambda e: webbrowser.open_new("https://github.com/xVRVx/winLoadXRAY/releases/")) - except: pass - -# --- Остальные утилиты --- -def highlight_active(tag): - global active_tag - active_tag = tag - save_state() - config_list.update_colors() - -def clear_highlight(): - global active_tag - active_tag = None - save_state() - config_list.update_colors() - -class ToolTip: - def __init__(self, widget, text): - self.widget = widget - self.text = text - self.tipwindow = None - self.widget.bind("", self.show_tip) - self.widget.bind("", self.hide_tip) - - def show_tip(self, event=None): - if self.tipwindow or not self.text: return - x = self.widget.winfo_rootx() + 20 - y = self.widget.winfo_rooty() + self.widget.winfo_height() + 10 - self.tipwindow = tw = tk.Toplevel(self.widget) - tw.wm_overrideredirect(True) - tw.wm_geometry(f"+{x}+{y}") - label = tk.Label(tw, text=self.text, background="#ffffe0", relief="solid", borderwidth=1, font=("tahoma", "8", "normal")) - label.pack(ipadx=4, ipady=2) - - def hide_tip(self, event=None): - if self.tipwindow: - self.tipwindow.destroy() - self.tipwindow = None - -def resource_path(relative_path): - if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) - return os.path.join(os.path.dirname(os.path.abspath(__file__)), relative_path) - -XRAY_EXE = resource_path("xray/xray.exe") -CREATE_NO_WINDOW = 0x08000000 - -def get_executable_path(): - return sys.executable if getattr(sys, 'frozen', False) else os.path.abspath(__file__) - -def is_in_startup(app_name=APP_NAME): - try: - with winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, winreg.KEY_READ - ) as key: - value, _ = winreg.QueryValueEx(key, app_name) - exe_path = get_executable_path().lower() - return exe_path in value.lower() - except Exception: - return False - -def add_to_startup(app_name=APP_NAME, path=None): - if path is None: - path = get_executable_path() - path = f'"{path}" --autostart' - try: - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, winreg.KEY_ALL_ACCESS - ) - winreg.SetValueEx(key, app_name, 0, winreg.REG_SZ, path) - winreg.CloseKey(key) - except Exception as e: - print("Ошибка добавления в автозапуск:", e) - -def remove_from_startup(app_name=APP_NAME): - try: - key = winreg.OpenKey( - winreg.HKEY_CURRENT_USER, - r"Software\Microsoft\Windows\CurrentVersion\Run", - 0, winreg.KEY_ALL_ACCESS - ) - winreg.DeleteValue(key, app_name) - winreg.CloseKey(key) - except FileNotFoundError: - pass - except Exception as e: - print("Ошибка удаления из автозапуска:", e) - -def toggle_startup(): - if startup_var.get(): - add_to_startup() - else: - remove_from_startup() - -def is_admin(): - try: return ctypes.windll.shell32.IsUserAnAdmin() - except: return False - -def run_as_admin(): - try: - exe_path = get_executable_path() - if exe_path.endswith('.py'): - ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, f'"{exe_path}"', None, 1) - else: - ctypes.windll.shell32.ShellExecuteW(None, "runas", exe_path, "", None, 1) - - save_state() - stop_xray(is_quitting=True) - stop_system_proxy(is_quitting=True) - os._exit(0) # Убиваем жестко, чтобы потоки трея не зависали - except Exception as e: - messagebox.showerror("Ошибка", f"Не удалось получить права администратора: {e}") - -def open_link(event): webbrowser.open_new("https://t.me/SkyBridge_VPN_bot") -def github(event): webbrowser.open_new("https://github.com/xVRVx/winLoadXRAY/") - -# --- Обработка ссылки (Протокол Реестра) --- -def register_url_protocol(): - if sys.platform != "win32": return - try: - exe_path = get_executable_path() - if exe_path.endswith('.py'): - command = f'"{sys.executable}" "{exe_path}" "%1"' - else: - command = f'"{exe_path}" "%1"' - - key_path = r"Software\Classes\winloadxray" - with winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path) as key: - winreg.SetValueEx(key, "", 0, winreg.REG_SZ, "URL:winLoadXRAY Protocol") - winreg.SetValueEx(key, "URL Protocol", 0, winreg.REG_SZ, "") - - cmd_path = key_path + r"\shell\open\command" - with winreg.CreateKey(winreg.HKEY_CURRENT_USER, cmd_path) as key: - winreg.SetValueEx(key, "", 0, winreg.REG_SZ, command) - except Exception: - pass - -def _restore_window(): - # Полностью восстанавливаем окно (включая возврат на панель задач) - root.deiconify() - root.wm_state('normal') - root.attributes('-topmost', True) - root.attributes('-topmost', False) - root.focus_force() - -def process_incoming_url(url): - try: - _restore_window() - except: pass - - if url and url != "WAKEUP": - entry.delete(0, 'end') - entry.insert(0, url) - add_from_url() - -def start_ipc_server(): - def _server(): - try: - server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server.bind(("127.0.0.1", IPC_PORT)) - server.listen() - while True: - conn, addr = server.accept() - data = conn.recv(4096) - if data: - url = data.decode('utf-8').strip() - if url: - root.after(0, lambda u=url: process_incoming_url(u)) - except Exception: - pass - threading.Thread(target=_server, daemon=True).start() - - -# ========================================== -# ====== НАСТРОЙКА ЦВЕТОВ ПРОГРАММЫ ======== -# ========================================== -MAIN_BG_COLOR = "#102236" # Основной цвет программы (темно-синий) -LIST_BG_COLOR = "#1a1a1a" # Цвет поля со списком конфигов (темный/графит) -# ========================================== - - -class ConfigList(ctk.CTkScrollableFrame): - def __init__(self, master, command=None, right_click_command=None, **kwargs): - super().__init__(master, **kwargs) - self.command = command - self.right_click_command = right_click_command - self.rows = {} - self.selected_tag = None - self.emoji_font = ctk.CTkFont(family="Segoe UI Emoji", size=16) - - def insert(self, tag, config_type="XRAY"): - if tag in self.rows: return - - row_frame = ctk.CTkFrame(self, fg_color="transparent", corner_radius=0, border_width=0) - row_frame.pack(fill="x", padx=2, pady=1) - - flag, name = split_flag(tag) - - lbl_flag = ctk.CTkLabel(row_frame, text=flag, width=30, font=self.emoji_font, anchor="center", text_color="white") - lbl_flag.pack(side="left", padx=(5,0)) - - lbl_name = ctk.CTkLabel(row_frame, text=name, anchor="w", text_color="white") - lbl_name.pack(side="left", fill="x", expand=True, padx=5) - - # --- Правый блок --- - lbl_ping = ctk.CTkLabel(row_frame, text="", width=55, anchor="e", text_color="white") - lbl_ping.pack(side="right", padx=(0,10)) - - lbl_type = ctk.CTkLabel(row_frame, text=config_type, width=90, anchor="w", text_color="gray") - lbl_type.pack(side="right", padx=(5, 5)) - - for w in (row_frame, lbl_flag, lbl_name, lbl_ping, lbl_type): - w.bind("", lambda e, t=tag: self.select(t)) - w.bind("", lambda e, t=tag: self.right_click(e, t)) - w.bind("", lambda e, t=tag: self.double_click(t)) - w.configure(cursor="hand2") - - self.rows[tag] = { - "frame": row_frame, - "lbl_name": lbl_name, - "lbl_ping": lbl_ping, - "lbl_type": lbl_type, - "lbl_flag": lbl_flag - } - - def update_colors(self): - for t, widgets in self.rows.items(): - if t == active_tag: - bg_color = "#2ECC71" - widgets["frame"].configure(fg_color=bg_color) - widgets["lbl_name"].configure(text_color="black", fg_color=bg_color) - widgets["lbl_ping"].configure(text_color="black", fg_color=bg_color) - widgets["lbl_type"].configure(text_color="#1E5631", fg_color=bg_color) - widgets["lbl_flag"].configure(text_color="black", fg_color=bg_color) - elif t == self.selected_tag: - bg_color = "#3498DB" - widgets["frame"].configure(fg_color=bg_color) - widgets["lbl_name"].configure(text_color="white", fg_color=bg_color) - widgets["lbl_ping"].configure(text_color="white", fg_color=bg_color) - widgets["lbl_type"].configure(text_color="#D5D8DC", fg_color=bg_color) - widgets["lbl_flag"].configure(text_color="white", fg_color=bg_color) - else: - widgets["frame"].configure(fg_color="transparent") - widgets["lbl_name"].configure(text_color=["white", "gray90"], fg_color="transparent") - widgets["lbl_ping"].configure(text_color=["white", "gray90"], fg_color="transparent") - widgets["lbl_type"].configure(text_color="gray", fg_color="transparent") - widgets["lbl_flag"].configure(text_color=["white", "gray90"], fg_color="transparent") - - def update_ping(self, tag, ping_text): - if tag in self.rows: - self.rows[tag]["lbl_ping"].configure(text=ping_text) - - def delete(self, tag): - if tag in self.rows: - self.rows[tag]["frame"].destroy() - del self.rows[tag] - if self.selected_tag == tag: self.selected_tag = None - - def clear(self): - for w in self.rows.values(): w["frame"].destroy() - self.rows.clear() - self.selected_tag = None - - def select(self, tag): - self.selected_tag = tag - self.update_colors() - if self.command: self.command(tag) - - def right_click(self, event, tag): - self.select(tag) - if self.right_click_command: self.right_click_command(event, tag) - - def double_click(self, tag): - self.select(tag) - run_selected() - - def get_all_tags(self): return list(self.rows.keys()) - - -root = ctk.CTk() -root.title(f"{APP_NAME} {APP_VERS} {XRAY_VERS}") -root.geometry("500x450") -root.minsize(500, 450) -root.iconbitmap(resource_path("img/icon.ico")) - -root.configure(fg_color=MAIN_BG_COLOR) - -def keypress(e): - if e.keycode == 86: paste_and_add() # Ctrl+V - elif e.keycode == 67: cmd_copy(root) - elif e.keycode == 88: cmd_cut(root) - elif e.keycode == 65: cmd_select_all(root) -root.bind("", keypress) -root.bind('', lambda e: add_from_url() if entry == root.focus_get() else run_selected()) - -bg_frame = ctk.CTkFrame(root, fg_color="transparent") -bg_frame.pack(fill="both", expand=True) - -content_frame = ctk.CTkFrame(bg_frame, fg_color="transparent") -content_frame.pack(fill="both", expand=True, padx=15, pady=15) - -# Меню профилей -frame_prof = ctk.CTkFrame(content_frame, fg_color="transparent") -frame_prof.pack(fill="x", pady=(0, 10)) -ctk.CTkLabel(frame_prof, text="Профиль:", text_color="white").pack(side="left", padx=(0,5)) -profile_var = ctk.StringVar(value="Default") -profile_dropdown = ctk.CTkOptionMenu(frame_prof, variable=profile_var, command=switch_profile) -profile_dropdown.pack(side="left", fill="x", expand=True, padx=(0, 5)) -ctk.CTkButton(frame_prof, text="+", width=30, command=add_new_profile).pack(side="left", padx=(0, 5)) -ctk.CTkButton(frame_prof, text="-", width=30, fg_color="#E74C3C", hover_color="#C0392B", command=delete_current_profile).pack(side="left") - -# Строка ввода -frame_entry = ctk.CTkFrame(content_frame, fg_color="transparent") -frame_entry.pack(fill="x", pady=(0, 10)) -entry = ctk.CTkEntry(frame_entry, placeholder_text="URL подписки или конфиг...") -entry.pack(side="left", fill="x", expand=True, padx=(0, 5)) -ToolTip(entry, "Вставьте сюда URL подписки или конфига XRAY") - -img_paste = ctk.CTkImage(Image.open(resource_path("img/ref.png")), size=(20, 20)) -btn_paste = ctk.CTkButton(frame_entry, image=img_paste, text="", width=30, command=paste_and_add) -btn_paste.pack(side="left", padx=(0, 5)) -ToolTip(btn_paste, "Вставить из буфера обмена") - -img_update = ctk.CTkImage(Image.open(resource_path("img/ico.png")), size=(20, 20)) -btn_refresh = ctk.CTkButton(frame_entry, image=img_update, text="", width=30, command=update_all_subscriptions) -btn_refresh.pack(side="left") -ToolTip(btn_refresh, "Обновить подписку") - -# Список конфигов -context_menu = tk.Menu(root, tearoff=0, bg="#2b2b2b", fg="white", activebackground="#3498db") -context_menu.add_command(label="SNI Ping", command=on_context_ping_click) -context_menu.add_command(label="Удалить конфиг", command=on_context_delete_config) - -config_list = ConfigList(content_frame, fg_color=LIST_BG_COLOR, right_click_command=show_context_menu, width=400, height=150) -config_list.pack(fill="both", expand=True, pady=(0, 5)) - -# Кнопки управления -frame_btns1 = ctk.CTkFrame(content_frame, fg_color="transparent") -frame_btns1.pack(fill="x", pady=(15, 10)) -btn_run = ctk.CTkButton(frame_btns1, text="Запустить конфиг", command=run_selected) -btn_run.pack(side="left", fill="x", expand=True, padx=(0, 5)) -ToolTip(btn_run, "socks5 на 2080 порту") - -btn_proxy = ctk.CTkButton(frame_btns1, text="Включить системный прокси", command=toggle_system_proxy) -btn_proxy.pack(side="right", fill="x", expand=True) -ToolTip(btn_proxy, "Запустите конфиг и выключите другие прокси расширения.\nРаботает только для браузеров.") - -frame_btns2 = ctk.CTkFrame(content_frame, fg_color="transparent") -frame_btns2.pack(fill="x", pady=(5, 5)) -startup_var = ctk.BooleanVar(value=is_in_startup()) -ctk.CTkCheckBox(frame_btns2, text="Автозапуск", variable=startup_var, command=toggle_startup, text_color="white").pack(side="left") -btn_auto = ctk.CTkButton(frame_btns2, text="Автовыбор", width=100, command=on_auto_select_click) -btn_auto.pack(side="left", padx=(15, 5)) -ToolTip(btn_auto, "Случайный выбор") - -btn_tun = ctk.CTkButton(frame_btns2, text="Включить TUN", width=120, command=vrv_tun_mode_toggle) -btn_tun.pack(side="right") -ToolTip(btn_tun, "Только от имени Администратора! Ожидание VPN 30 сек!\nСоздается виртуальная сетевая карта.") - -# Ссылки -frame_links = ctk.CTkFrame(content_frame, fg_color="transparent") -frame_links.pack(fill="x", pady=(10, 0)) -lbl_tg = ctk.CTkLabel(frame_links, text="Наш Telegram бот", cursor="hand2", text_color="#3498db") -lbl_tg.pack(side="left", padx=(0, 10)) -lbl_tg.bind("", open_link) -lbl_gh = ctk.CTkLabel(frame_links, text="GitHub", cursor="hand2", text_color="#3498db") -lbl_gh.pack(side="left") -lbl_gh.bind("", github) - -# ========================================== -# ===== ЛОГИКА СИСТЕМНОГО ТРЕЯ И ВЫХОДА ==== -# ========================================== - -def actual_quit(): - stop_xray(is_quitting=True) - stop_system_proxy(is_quitting=True) - stop_tun2proxy() - root.destroy() - os._exit(0) # Жесткий выход для завершения всех фоновых потоков (в т.ч. трея) - -def on_closing(): - if HAS_TRAY: - root.withdraw() # Скрываем программу в системный трей вместо закрытия - else: - actual_quit() - -def show_window_from_tray(icon=None, item=None): - root.after(0, _restore_window) - -def quit_from_tray(icon=None, item=None): - if icon: - icon.stop() - root.after(0, actual_quit) - -if HAS_TRAY: - def setup_tray(): - try: - image = Image.open(resource_path("img/icon.ico")) - menu = pystray.Menu( - tray_item('Развернуть', show_window_from_tray, default=True), - tray_item('Выход', quit_from_tray) - ) - tray_icon = pystray.Icon("winLoadXRAY", image, "winLoadXRAY", menu) - threading.Thread(target=tray_icon.run, daemon=True).start() - except: - pass - setup_tray() - - -# --- Инициализация перед запуском --- -register_url_protocol() -start_ipc_server() - -# Запуск -load_initial_profile() -profile_var.set(active_profile) -profile_dropdown.configure(values=get_profiles()) -load_base64_urls() -load_state(is_initial=True) - -if startup_url: - root.after(200, lambda: process_incoming_url(startup_url)) - -if IS_AUTOSTART: - if HAS_TRAY: - root.withdraw() # Полностью скрываем с панели задач - else: - root.iconify() - -root.after(3000, check_latest_version) - -root.protocol("WM_DELETE_WINDOW", on_closing) -root.mainloop() \ No newline at end of file