mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-08-02 08:35:48 +03:00
Общий UI трея в ui/, тултипы, исправление tg:// с реальным host, доработки windows.py (импорты, lock, IPv6, остановка прокси) (#417)
This commit is contained in:
+204
-271
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ctypes
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
@@ -11,16 +12,48 @@ import sys
|
||||
import threading
|
||||
import time
|
||||
import webbrowser
|
||||
import pyperclip
|
||||
import asyncio as _asyncio
|
||||
from pathlib import Path
|
||||
from typing import Dict, Optional
|
||||
|
||||
import pystray
|
||||
import customtkinter as ctk
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
try:
|
||||
import pyperclip
|
||||
except ImportError:
|
||||
pyperclip = None
|
||||
|
||||
try:
|
||||
import pystray
|
||||
except ImportError:
|
||||
pystray = None
|
||||
|
||||
try:
|
||||
import customtkinter as ctk
|
||||
except ImportError:
|
||||
ctk = None
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
except ImportError:
|
||||
Image = ImageDraw = ImageFont = None
|
||||
|
||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||
from proxy import __version__
|
||||
from utils.default_config import default_tray_config
|
||||
from ui.ctk_tray_ui import (
|
||||
install_tray_config_buttons,
|
||||
install_tray_config_form,
|
||||
populate_first_run_window,
|
||||
tray_settings_scroll_and_footer,
|
||||
validate_config_form,
|
||||
)
|
||||
from ui.ctk_theme import (
|
||||
CONFIG_DIALOG_FRAME_PAD,
|
||||
CONFIG_DIALOG_SIZE,
|
||||
FIRST_RUN_SIZE,
|
||||
create_ctk_root,
|
||||
ctk_theme_for_platform,
|
||||
main_content_frame,
|
||||
)
|
||||
|
||||
|
||||
IS_FROZEN = bool(getattr(sys, "frozen", False))
|
||||
@@ -33,16 +66,7 @@ FIRST_RUN_MARKER = APP_DIR / ".first_run_done"
|
||||
IPV6_WARN_MARKER = APP_DIR / ".ipv6_warned"
|
||||
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"port": 1080,
|
||||
"host": "127.0.0.1",
|
||||
"dc_ip": ["2:149.154.167.220", "4:149.154.167.220"],
|
||||
"verbose": False,
|
||||
"autostart": False,
|
||||
"log_max_mb": 5,
|
||||
"buf_kb": 256,
|
||||
"pool_size": 4,
|
||||
}
|
||||
DEFAULT_CONFIG = default_tray_config()
|
||||
|
||||
|
||||
_proxy_thread: Optional[threading.Thread] = None
|
||||
@@ -54,6 +78,15 @@ _lock_file_path: Optional[Path] = None
|
||||
|
||||
log = logging.getLogger("tg-ws-tray")
|
||||
|
||||
_user32 = ctypes.windll.user32
|
||||
_user32.MessageBoxW.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_wchar_p,
|
||||
ctypes.c_wchar_p,
|
||||
ctypes.c_uint,
|
||||
]
|
||||
_user32.MessageBoxW.restype = ctypes.c_int
|
||||
|
||||
|
||||
def _same_process(lock_meta: dict, proc: psutil.Process) -> bool:
|
||||
try:
|
||||
@@ -64,9 +97,18 @@ def _same_process(lock_meta: dict, proc: psutil.Process) -> bool:
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
try:
|
||||
for arg in proc.cmdline():
|
||||
if "windows.py" in arg:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
frozen = bool(getattr(sys, "frozen", False))
|
||||
if frozen:
|
||||
return os.path.basename(sys.executable) == proc.name()
|
||||
return (
|
||||
os.path.basename(sys.executable).lower() == proc.name().lower()
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
@@ -330,7 +372,11 @@ def stop_proxy():
|
||||
loop, stop_ev = _async_stop
|
||||
loop.call_soon_threadsafe(stop_ev.set)
|
||||
if _proxy_thread:
|
||||
_proxy_thread.join(timeout=2)
|
||||
_proxy_thread.join(timeout=5)
|
||||
if _proxy_thread.is_alive():
|
||||
log.warning(
|
||||
"Proxy thread did not finish within timeout; "
|
||||
"the process may still exit shortly")
|
||||
_proxy_thread = None
|
||||
log.info("Proxy stopped")
|
||||
|
||||
@@ -343,16 +389,61 @@ def restart_proxy():
|
||||
|
||||
|
||||
def _show_error(text: str, title: str = "TG WS Proxy — Ошибка"):
|
||||
ctypes.windll.user32.MessageBoxW(0, text, title, 0x10)
|
||||
_user32.MessageBoxW(None, text, title, 0x10)
|
||||
|
||||
|
||||
def _show_info(text: str, title: str = "TG WS Proxy"):
|
||||
ctypes.windll.user32.MessageBoxW(0, text, title, 0x40)
|
||||
_user32.MessageBoxW(None, text, title, 0x40)
|
||||
|
||||
|
||||
def _ask_open_release_page(latest_version: str, url: str) -> bool:
|
||||
"""Win32 Yes/No: открыть страницу релиза."""
|
||||
MB_YESNO = 0x4
|
||||
MB_ICONQUESTION = 0x20
|
||||
IDYES = 6
|
||||
text = (
|
||||
f"Доступна новая версия: {latest_version}\n\n"
|
||||
f"Открыть страницу релиза в браузере?"
|
||||
)
|
||||
r = _user32.MessageBoxW(
|
||||
None,
|
||||
text,
|
||||
"TG WS Proxy — обновление",
|
||||
MB_YESNO | MB_ICONQUESTION,
|
||||
)
|
||||
return r == IDYES
|
||||
|
||||
|
||||
def _maybe_notify_update_async():
|
||||
"""
|
||||
Фоновая проверка GitHub Releases и уведомление (не блокирует трей).
|
||||
"""
|
||||
def _work():
|
||||
time.sleep(1.5)
|
||||
if _exiting:
|
||||
return
|
||||
if not _config.get("check_updates", True):
|
||||
return
|
||||
try:
|
||||
from utils.update_check import RELEASES_PAGE_URL, get_status, run_check
|
||||
run_check(__version__)
|
||||
st = get_status()
|
||||
if not st.get("has_update"):
|
||||
return
|
||||
url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL
|
||||
ver = st.get("latest") or "?"
|
||||
if _ask_open_release_page(str(ver), url):
|
||||
webbrowser.open(url)
|
||||
except Exception as exc:
|
||||
log.debug("Update check failed: %s", exc)
|
||||
|
||||
threading.Thread(target=_work, daemon=True, name="update-check").start()
|
||||
|
||||
|
||||
def _on_open_in_telegram(icon=None, item=None):
|
||||
host = _config.get("host", DEFAULT_CONFIG["host"])
|
||||
port = _config.get("port", DEFAULT_CONFIG["port"])
|
||||
url = f"tg://socks?server=127.0.0.1&port={port}"
|
||||
url = f"tg://socks?server={host}&port={port}"
|
||||
log.info("Opening %s", url)
|
||||
try:
|
||||
result = webbrowser.open(url)
|
||||
@@ -360,6 +451,11 @@ def _on_open_in_telegram(icon=None, item=None):
|
||||
raise RuntimeError("webbrowser.open returned False")
|
||||
except Exception:
|
||||
log.info("Browser open failed, copying to clipboard")
|
||||
if pyperclip is None:
|
||||
_show_error(
|
||||
"Не удалось открыть Telegram автоматически.\n\n"
|
||||
f"Установите пакет pyperclip для копирования в буфер или откройте вручную:\n{url}")
|
||||
return
|
||||
try:
|
||||
pyperclip.copy(url)
|
||||
_show_info(
|
||||
@@ -387,165 +483,53 @@ def _edit_config_dialog():
|
||||
cfg = dict(_config)
|
||||
cfg["autostart"] = is_autostart_enabled()
|
||||
|
||||
# Make sure that the autostart key is removed if autostart
|
||||
# Make sure that the autostart key is removed if autostart
|
||||
# is disabled, even if the executable file is moved.
|
||||
if _supports_autostart() and not cfg["autostart"]:
|
||||
set_autostart_enabled(False)
|
||||
|
||||
ctk.set_appearance_mode("light")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
root = ctk.CTk()
|
||||
root.title("TG WS Proxy — Настройки")
|
||||
root.resizable(False, False)
|
||||
root.attributes("-topmost", True)
|
||||
icon_path = str(Path(__file__).parent / "icon.ico")
|
||||
root.iconbitmap(icon_path)
|
||||
|
||||
TG_BLUE = "#3390ec"
|
||||
TG_BLUE_HOVER = "#2b7cd4"
|
||||
BG = "#ffffff"
|
||||
FIELD_BG = "#f0f2f5"
|
||||
FIELD_BORDER = "#d6d9dc"
|
||||
TEXT_PRIMARY = "#000000"
|
||||
TEXT_SECONDARY = "#707579"
|
||||
FONT_FAMILY = "Segoe UI"
|
||||
|
||||
w, h = 420, 540
|
||||
|
||||
if _supports_autostart():
|
||||
h += 70
|
||||
|
||||
sw = root.winfo_screenwidth()
|
||||
sh = root.winfo_screenheight()
|
||||
root.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")
|
||||
root.configure(fg_color=BG)
|
||||
|
||||
frame = ctk.CTkFrame(root, fg_color=BG, corner_radius=0)
|
||||
frame.pack(fill="both", expand=True, padx=24, pady=20)
|
||||
|
||||
# Host
|
||||
ctk.CTkLabel(frame, text="IP-адрес прокси",
|
||||
font=(FONT_FAMILY, 13), text_color=TEXT_PRIMARY,
|
||||
anchor="w").pack(anchor="w", pady=(0, 4))
|
||||
host_var = ctk.StringVar(value=cfg.get("host", "127.0.0.1"))
|
||||
host_entry = ctk.CTkEntry(frame, textvariable=host_var, width=200, height=36,
|
||||
font=(FONT_FAMILY, 13), corner_radius=10,
|
||||
fg_color=FIELD_BG, border_color=FIELD_BORDER,
|
||||
border_width=1, text_color=TEXT_PRIMARY)
|
||||
host_entry.pack(anchor="w", pady=(0, 12))
|
||||
|
||||
# Port
|
||||
ctk.CTkLabel(frame, text="Порт прокси",
|
||||
font=(FONT_FAMILY, 13), text_color=TEXT_PRIMARY,
|
||||
anchor="w").pack(anchor="w", pady=(0, 4))
|
||||
port_var = ctk.StringVar(value=str(cfg.get("port", 1080)))
|
||||
port_entry = ctk.CTkEntry(frame, textvariable=port_var, width=120, height=36,
|
||||
font=(FONT_FAMILY, 13), corner_radius=10,
|
||||
fg_color=FIELD_BG, border_color=FIELD_BORDER,
|
||||
border_width=1, text_color=TEXT_PRIMARY)
|
||||
port_entry.pack(anchor="w", pady=(0, 12))
|
||||
|
||||
# DC-IP mappings
|
||||
ctk.CTkLabel(frame, text="DC → IP маппинги (по одному на строку, формат DC:IP)",
|
||||
font=(FONT_FAMILY, 13), text_color=TEXT_PRIMARY,
|
||||
anchor="w").pack(anchor="w", pady=(0, 4))
|
||||
dc_textbox = ctk.CTkTextbox(frame, width=370, height=120,
|
||||
font=("Consolas", 12), corner_radius=10,
|
||||
fg_color=FIELD_BG, border_color=FIELD_BORDER,
|
||||
border_width=1, text_color=TEXT_PRIMARY)
|
||||
dc_textbox.pack(anchor="w", pady=(0, 12))
|
||||
dc_textbox.insert("1.0", "\n".join(cfg.get("dc_ip", DEFAULT_CONFIG["dc_ip"])))
|
||||
|
||||
# Verbose
|
||||
verbose_var = ctk.BooleanVar(value=cfg.get("verbose", False))
|
||||
ctk.CTkCheckBox(frame, text="Подробное логирование (verbose)",
|
||||
variable=verbose_var, font=(FONT_FAMILY, 13),
|
||||
text_color=TEXT_PRIMARY,
|
||||
fg_color=TG_BLUE, hover_color=TG_BLUE_HOVER,
|
||||
corner_radius=6, border_width=2,
|
||||
border_color=FIELD_BORDER).pack(anchor="w", pady=(0, 8))
|
||||
|
||||
# Advanced: buf_kb, pool_size, log_max_mb
|
||||
adv_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
||||
adv_frame.pack(anchor="w", fill="x", pady=(4, 8))
|
||||
|
||||
for col, (lbl, key, w_) in enumerate([
|
||||
("Буфер (KB, 256 default)", "buf_kb", 120),
|
||||
("WS пулов (4 default)", "pool_size", 120),
|
||||
("Log size (MB, 5 def)", "log_max_mb", 120),
|
||||
]):
|
||||
col_frame = ctk.CTkFrame(adv_frame, fg_color="transparent")
|
||||
col_frame.pack(side="left", padx=(0, 10))
|
||||
ctk.CTkLabel(col_frame, text=lbl, font=(FONT_FAMILY, 11),
|
||||
text_color=TEXT_SECONDARY, anchor="w").pack(anchor="w")
|
||||
ctk.CTkEntry(col_frame, width=w_, height=30, font=(FONT_FAMILY, 12),
|
||||
corner_radius=8, fg_color=FIELD_BG,
|
||||
border_color=FIELD_BORDER, border_width=1,
|
||||
text_color=TEXT_PRIMARY,
|
||||
textvariable=ctk.StringVar(
|
||||
value=str(cfg.get(key, DEFAULT_CONFIG[key]))
|
||||
)).pack(anchor="w")
|
||||
|
||||
_adv_entries = list(adv_frame.winfo_children())
|
||||
_adv_keys = ["buf_kb", "pool_size", "log_max_mb"]
|
||||
|
||||
autostart_var = None
|
||||
theme = ctk_theme_for_platform()
|
||||
w, h = CONFIG_DIALOG_SIZE
|
||||
if _supports_autostart():
|
||||
autostart_var = ctk.BooleanVar(value=cfg["autostart"])
|
||||
ctk.CTkCheckBox(frame, text="Автозапуск при включении Windows",
|
||||
variable=autostart_var, font=(FONT_FAMILY, 13),
|
||||
text_color=TEXT_PRIMARY,
|
||||
fg_color=TG_BLUE, hover_color=TG_BLUE_HOVER,
|
||||
corner_radius=6, border_width=2,
|
||||
border_color=FIELD_BORDER).pack(anchor="w", pady=(0, 8))
|
||||
ctk.CTkLabel(frame, text="При перемещении файла или открытии из другой папки\nавтозапуск будет сброшен",
|
||||
font=(FONT_FAMILY, 13), text_color=TEXT_SECONDARY,
|
||||
anchor="w", justify="left").pack(anchor="w", pady=(0, 8))
|
||||
h += 100
|
||||
|
||||
icon_path = str(Path(__file__).parent / "icon.ico")
|
||||
|
||||
root = create_ctk_root(
|
||||
ctk,
|
||||
title="TG WS Proxy — Настройки",
|
||||
width=w,
|
||||
height=h,
|
||||
theme=theme,
|
||||
after_create=lambda r: r.iconbitmap(icon_path),
|
||||
)
|
||||
|
||||
fpx, fpy = CONFIG_DIALOG_FRAME_PAD
|
||||
frame = main_content_frame(ctk, root, theme, padx=fpx, pady=fpy)
|
||||
|
||||
scroll, footer = tray_settings_scroll_and_footer(ctk, frame, theme)
|
||||
|
||||
widgets = install_tray_config_form(
|
||||
ctk,
|
||||
scroll,
|
||||
theme,
|
||||
cfg,
|
||||
DEFAULT_CONFIG,
|
||||
show_autostart=_supports_autostart(),
|
||||
autostart_value=cfg.get("autostart", False),
|
||||
)
|
||||
|
||||
def on_save():
|
||||
import socket as _sock
|
||||
host_val = host_var.get().strip()
|
||||
try:
|
||||
_sock.inet_aton(host_val)
|
||||
except OSError:
|
||||
_show_error("Некорректный IP-адрес.")
|
||||
merged = validate_config_form(
|
||||
widgets,
|
||||
DEFAULT_CONFIG,
|
||||
include_autostart=_supports_autostart(),
|
||||
)
|
||||
if isinstance(merged, str):
|
||||
_show_error(merged)
|
||||
return
|
||||
|
||||
try:
|
||||
port_val = int(port_var.get().strip())
|
||||
if not (1 <= port_val <= 65535):
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
_show_error("Порт должен быть числом 1-65535")
|
||||
return
|
||||
|
||||
lines = [l.strip() for l in dc_textbox.get("1.0", "end").strip().splitlines()
|
||||
if l.strip()]
|
||||
try:
|
||||
tg_ws_proxy.parse_dc_ip_list(lines)
|
||||
except ValueError as e:
|
||||
_show_error(str(e))
|
||||
return
|
||||
|
||||
new_cfg = {
|
||||
"host": host_val,
|
||||
"port": port_val,
|
||||
"dc_ip": lines,
|
||||
"verbose": verbose_var.get(),
|
||||
"autostart": (autostart_var.get() if autostart_var is not None else False),
|
||||
}
|
||||
|
||||
for i, key in enumerate(_adv_keys):
|
||||
col_frame = _adv_entries[i]
|
||||
entry = col_frame.winfo_children()[1]
|
||||
try:
|
||||
val = float(entry.get().strip())
|
||||
if key in ("buf_kb", "pool_size"):
|
||||
val = int(val)
|
||||
new_cfg[key] = val
|
||||
except ValueError:
|
||||
new_cfg[key] = DEFAULT_CONFIG[key]
|
||||
new_cfg = merged
|
||||
save_config(new_cfg)
|
||||
_config.update(new_cfg)
|
||||
log.info("Config saved: %s", new_cfg)
|
||||
@@ -555,6 +539,8 @@ def _edit_config_dialog():
|
||||
|
||||
_tray_icon.menu = _build_menu()
|
||||
|
||||
# Win32 MessageBox из того же потока, что и mainloop CTk, блокирует обработку Tcl/Tk
|
||||
# и даёт зависание; tkinter.messagebox согласован с циклом окна.
|
||||
from tkinter import messagebox
|
||||
if messagebox.askyesno("Перезапустить?",
|
||||
"Настройки сохранены.\n\n"
|
||||
@@ -568,21 +554,18 @@ def _edit_config_dialog():
|
||||
def on_cancel():
|
||||
root.destroy()
|
||||
|
||||
btn_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
||||
btn_frame.pack(fill="x", pady=(20, 0))
|
||||
ctk.CTkButton(btn_frame, text="Сохранить", height=38,
|
||||
font=(FONT_FAMILY, 14, "bold"), corner_radius=10,
|
||||
fg_color=TG_BLUE, hover_color=TG_BLUE_HOVER,
|
||||
text_color="#ffffff",
|
||||
command=on_save).pack(side="left", fill="x", expand=True, padx=(0, 8))
|
||||
ctk.CTkButton(btn_frame, text="Отмена", height=38,
|
||||
font=(FONT_FAMILY, 14), corner_radius=10,
|
||||
fg_color=FIELD_BG, hover_color=FIELD_BORDER,
|
||||
text_color=TEXT_PRIMARY, border_width=1,
|
||||
border_color=FIELD_BORDER,
|
||||
command=on_cancel).pack(side="right", fill="x", expand=True)
|
||||
install_tray_config_buttons(
|
||||
ctk, footer, theme, on_save=on_save, on_cancel=on_cancel)
|
||||
|
||||
root.mainloop()
|
||||
try:
|
||||
root.mainloop()
|
||||
finally:
|
||||
import tkinter as tk
|
||||
try:
|
||||
if root.winfo_exists():
|
||||
root.destroy()
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
|
||||
def _on_open_logs(icon=None, item=None):
|
||||
@@ -618,101 +601,41 @@ def _show_first_run():
|
||||
|
||||
host = _config.get("host", DEFAULT_CONFIG["host"])
|
||||
port = _config.get("port", DEFAULT_CONFIG["port"])
|
||||
tg_url = f"tg://socks?server={host}&port={port}"
|
||||
|
||||
if ctk is None:
|
||||
FIRST_RUN_MARKER.touch()
|
||||
return
|
||||
|
||||
ctk.set_appearance_mode("light")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
TG_BLUE = "#3390ec"
|
||||
TG_BLUE_HOVER = "#2b7cd4"
|
||||
BG = "#ffffff"
|
||||
FIELD_BG = "#f0f2f5"
|
||||
FIELD_BORDER = "#d6d9dc"
|
||||
TEXT_PRIMARY = "#000000"
|
||||
TEXT_SECONDARY = "#707579"
|
||||
FONT_FAMILY = "Segoe UI"
|
||||
|
||||
root = ctk.CTk()
|
||||
root.title("TG WS Proxy")
|
||||
root.resizable(False, False)
|
||||
root.attributes("-topmost", True)
|
||||
theme = ctk_theme_for_platform()
|
||||
icon_path = str(Path(__file__).parent / "icon.ico")
|
||||
root.iconbitmap(icon_path)
|
||||
w, h = FIRST_RUN_SIZE
|
||||
root = create_ctk_root(
|
||||
ctk,
|
||||
title="TG WS Proxy",
|
||||
width=w,
|
||||
height=h,
|
||||
theme=theme,
|
||||
after_create=lambda r: r.iconbitmap(icon_path),
|
||||
)
|
||||
|
||||
w, h = 520, 440
|
||||
sw = root.winfo_screenwidth()
|
||||
sh = root.winfo_screenheight()
|
||||
root.geometry(f"{w}x{h}+{(sw-w)//2}+{(sh-h)//2}")
|
||||
root.configure(fg_color=BG)
|
||||
|
||||
frame = ctk.CTkFrame(root, fg_color=BG, corner_radius=0)
|
||||
frame.pack(fill="both", expand=True, padx=28, pady=24)
|
||||
|
||||
title_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
||||
title_frame.pack(anchor="w", pady=(0, 16), fill="x")
|
||||
|
||||
# Blue accent bar
|
||||
accent_bar = ctk.CTkFrame(title_frame, fg_color=TG_BLUE,
|
||||
width=4, height=32, corner_radius=2)
|
||||
accent_bar.pack(side="left", padx=(0, 12))
|
||||
|
||||
ctk.CTkLabel(title_frame, text="Прокси запущен и работает в системном трее",
|
||||
font=(FONT_FAMILY, 17, "bold"),
|
||||
text_color=TEXT_PRIMARY).pack(side="left")
|
||||
|
||||
# Info sections
|
||||
sections = [
|
||||
("Как подключить Telegram Desktop:", True),
|
||||
(" Автоматически:", True),
|
||||
(f" ПКМ по иконке в трее → «Открыть в Telegram»", False),
|
||||
(f" Или ссылка: {tg_url}", False),
|
||||
("\n Вручную:", True),
|
||||
(" Настройки → Продвинутые → Тип подключения → Прокси", False),
|
||||
(f" SOCKS5 → {host} : {port} (без логина/пароля)", False),
|
||||
]
|
||||
|
||||
for text, bold in sections:
|
||||
weight = "bold" if bold else "normal"
|
||||
ctk.CTkLabel(frame, text=text,
|
||||
font=(FONT_FAMILY, 13, weight),
|
||||
text_color=TEXT_PRIMARY,
|
||||
anchor="w", justify="left").pack(anchor="w", pady=1)
|
||||
|
||||
# Spacer
|
||||
ctk.CTkFrame(frame, fg_color="transparent", height=16).pack()
|
||||
|
||||
# Separator
|
||||
ctk.CTkFrame(frame, fg_color=FIELD_BORDER, height=1,
|
||||
corner_radius=0).pack(fill="x", pady=(0, 12))
|
||||
|
||||
# Checkbox
|
||||
auto_var = ctk.BooleanVar(value=True)
|
||||
ctk.CTkCheckBox(frame, text="Открыть прокси в Telegram сейчас",
|
||||
variable=auto_var, font=(FONT_FAMILY, 13),
|
||||
text_color=TEXT_PRIMARY,
|
||||
fg_color=TG_BLUE, hover_color=TG_BLUE_HOVER,
|
||||
corner_radius=6, border_width=2,
|
||||
border_color=FIELD_BORDER).pack(anchor="w", pady=(0, 16))
|
||||
|
||||
def on_ok():
|
||||
def on_done(open_tg: bool):
|
||||
FIRST_RUN_MARKER.touch()
|
||||
open_tg = auto_var.get()
|
||||
root.destroy()
|
||||
if open_tg:
|
||||
_on_open_in_telegram()
|
||||
|
||||
ctk.CTkButton(frame, text="Начать", width=180, height=42,
|
||||
font=(FONT_FAMILY, 15, "bold"), corner_radius=10,
|
||||
fg_color=TG_BLUE, hover_color=TG_BLUE_HOVER,
|
||||
text_color="#ffffff",
|
||||
command=on_ok).pack(pady=(0, 0))
|
||||
populate_first_run_window(
|
||||
ctk, root, theme, host=host, port=port, on_done=on_done)
|
||||
|
||||
root.protocol("WM_DELETE_WINDOW", on_ok)
|
||||
root.mainloop()
|
||||
try:
|
||||
root.mainloop()
|
||||
finally:
|
||||
import tkinter as tk
|
||||
try:
|
||||
if root.winfo_exists():
|
||||
root.destroy()
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
|
||||
def _has_ipv6_enabled() -> bool:
|
||||
@@ -721,8 +644,15 @@ def _has_ipv6_enabled() -> bool:
|
||||
addrs = _sock.getaddrinfo(_sock.gethostname(), None, _sock.AF_INET6)
|
||||
for addr in addrs:
|
||||
ip = addr[4][0]
|
||||
if ip and not ip.startswith('::1') and not ip.startswith('fe80::1'):
|
||||
return True
|
||||
if not ip or ip.startswith("::1"):
|
||||
continue
|
||||
try:
|
||||
if ipaddress.IPv6Address(ip).is_link_local:
|
||||
continue
|
||||
except ValueError:
|
||||
if ip.startswith("fe80:"):
|
||||
continue
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
@@ -793,13 +723,14 @@ def run_tray():
|
||||
|
||||
setup_logging(_config.get("verbose", False),
|
||||
log_max_mb=_config.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"]))
|
||||
log.info("TG WS Proxy tray app starting")
|
||||
log.info("TG WS Proxy версия %s, tray app starting", __version__)
|
||||
log.info("Config: %s", _config)
|
||||
log.info("Log file: %s", LOG_FILE)
|
||||
|
||||
if pystray is None or Image is None:
|
||||
log.error("pystray or Pillow not installed; "
|
||||
"running in console mode")
|
||||
if pystray is None or Image is None or ctk is None:
|
||||
log.error(
|
||||
"pystray, Pillow or customtkinter not installed; "
|
||||
"running in console mode")
|
||||
start_proxy()
|
||||
try:
|
||||
while True:
|
||||
@@ -810,6 +741,8 @@ def run_tray():
|
||||
|
||||
start_proxy()
|
||||
|
||||
_maybe_notify_update_async()
|
||||
|
||||
_show_first_run()
|
||||
_check_ipv6_warning()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user