feat(tray): версия в логах и настройках, UI настроек, фикс Tk при выходе
Реализует предложение из issue #430: версия в логах при старте, в окне настроек (CTk) и в меню macOS; прокрутка и вёрстка; защита Variable.__del__ и destroy корня окна. Refs: https://github.com/Flowseal/tg-ws-proxy/issues/430
This commit is contained in:
parent
86794f34a9
commit
35ea33ee3f
26
linux.py
26
linux.py
|
|
@ -19,10 +19,12 @@ import pystray
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||||
|
from proxy import __version__
|
||||||
from ui.ctk_tray_ui import (
|
from ui.ctk_tray_ui import (
|
||||||
install_tray_config_buttons,
|
install_tray_config_buttons,
|
||||||
install_tray_config_form,
|
install_tray_config_form,
|
||||||
populate_first_run_window,
|
populate_first_run_window,
|
||||||
|
tray_settings_scroll_and_footer,
|
||||||
validate_config_form,
|
validate_config_form,
|
||||||
)
|
)
|
||||||
from ui.ctk_theme import (
|
from ui.ctk_theme import (
|
||||||
|
|
@ -395,8 +397,10 @@ def _edit_config_dialog():
|
||||||
fpx, fpy = CONFIG_DIALOG_FRAME_PAD
|
fpx, fpy = CONFIG_DIALOG_FRAME_PAD
|
||||||
frame = main_content_frame(ctk, root, theme, padx=fpx, pady=fpy)
|
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(
|
widgets = install_tray_config_form(
|
||||||
ctk, frame, theme, cfg, DEFAULT_CONFIG,
|
ctk, scroll, theme, cfg, DEFAULT_CONFIG,
|
||||||
show_autostart=False,
|
show_autostart=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -430,9 +434,17 @@ def _edit_config_dialog():
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
install_tray_config_buttons(
|
install_tray_config_buttons(
|
||||||
ctk, frame, theme, on_save=on_save, on_cancel=on_cancel)
|
ctk, footer, theme, on_save=on_save, on_cancel=on_cancel)
|
||||||
|
|
||||||
|
try:
|
||||||
root.mainloop()
|
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):
|
def _on_open_logs(icon=None, item=None):
|
||||||
|
|
@ -506,7 +518,15 @@ def _show_first_run():
|
||||||
populate_first_run_window(
|
populate_first_run_window(
|
||||||
ctk, root, theme, host=host, port=port, on_done=on_done)
|
ctk, root, theme, host=host, port=port, on_done=on_done)
|
||||||
|
|
||||||
|
try:
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
finally:
|
||||||
|
import tkinter as tk
|
||||||
|
try:
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.destroy()
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _has_ipv6_enabled() -> bool:
|
def _has_ipv6_enabled() -> bool:
|
||||||
|
|
@ -588,7 +608,7 @@ def run_tray():
|
||||||
|
|
||||||
setup_logging(_config.get("verbose", False),
|
setup_logging(_config.get("verbose", False),
|
||||||
log_max_mb=_config.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"]))
|
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("Config: %s", _config)
|
||||||
log.info("Log file: %s", LOG_FILE)
|
log.info("Log file: %s", LOG_FILE)
|
||||||
|
|
||||||
|
|
|
||||||
8
macos.py
8
macos.py
|
|
@ -30,6 +30,7 @@ except ImportError:
|
||||||
pyperclip = None
|
pyperclip = None
|
||||||
|
|
||||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||||
|
from proxy import __version__
|
||||||
|
|
||||||
APP_NAME = "TgWsProxy"
|
APP_NAME = "TgWsProxy"
|
||||||
APP_DIR = Path.home() / "Library" / "Application Support" / APP_NAME
|
APP_DIR = Path.home() / "Library" / "Application Support" / APP_NAME
|
||||||
|
|
@ -616,6 +617,9 @@ class TgWsProxyApp(_TgWsProxyAppBase):
|
||||||
self._logs_item = rumps.MenuItem(
|
self._logs_item = rumps.MenuItem(
|
||||||
"Открыть логи",
|
"Открыть логи",
|
||||||
callback=_on_open_logs)
|
callback=_on_open_logs)
|
||||||
|
self._version_item = rumps.MenuItem(
|
||||||
|
f"Версия {__version__}",
|
||||||
|
callback=lambda _: None)
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"TG WS Proxy",
|
"TG WS Proxy",
|
||||||
|
|
@ -628,6 +632,8 @@ class TgWsProxyApp(_TgWsProxyAppBase):
|
||||||
self._restart_item,
|
self._restart_item,
|
||||||
self._settings_item,
|
self._settings_item,
|
||||||
self._logs_item,
|
self._logs_item,
|
||||||
|
None,
|
||||||
|
self._version_item,
|
||||||
])
|
])
|
||||||
|
|
||||||
def update_menu_title(self):
|
def update_menu_title(self):
|
||||||
|
|
@ -651,7 +657,7 @@ def run_menubar():
|
||||||
|
|
||||||
setup_logging(_config.get("verbose", False),
|
setup_logging(_config.get("verbose", False),
|
||||||
log_max_mb=_config.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"]))
|
log_max_mb=_config.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"]))
|
||||||
log.info("TG WS Proxy menubar app starting")
|
log.info("TG WS Proxy версия %s, menubar app starting", __version__)
|
||||||
log.info("Config: %s", _config)
|
log.info("Config: %s", _config)
|
||||||
log.info("Log file: %s", LOG_FILE)
|
log.info("Log file: %s", LOG_FILE)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,35 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import tkinter
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Optional, Tuple
|
from typing import Any, Callable, Optional, Tuple
|
||||||
|
|
||||||
|
_tk_variable_del_guard_installed = False
|
||||||
|
|
||||||
|
|
||||||
|
def _install_tkinter_variable_del_guard() -> None:
|
||||||
|
"""
|
||||||
|
Убирает «Exception ignored» при выходе процесса: Tcl уже разрушен, а GC ещё
|
||||||
|
вызывает Variable.__del__ (StringVar и т.д.) — напр. окно CTk в фоновом потоке.
|
||||||
|
"""
|
||||||
|
global _tk_variable_del_guard_installed
|
||||||
|
if _tk_variable_del_guard_installed:
|
||||||
|
return
|
||||||
|
_orig = tkinter.Variable.__del__
|
||||||
|
|
||||||
|
def _safe_variable_del(self: Any, _orig: Any = _orig) -> None:
|
||||||
|
try:
|
||||||
|
_orig(self)
|
||||||
|
except (RuntimeError, tkinter.TclError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
tkinter.Variable.__del__ = _safe_variable_del # type: ignore[assignment]
|
||||||
|
_tk_variable_del_guard_installed = True
|
||||||
|
|
||||||
# Размеры и отступы (единые для диалогов настроек и первого запуска)
|
# Размеры и отступы (единые для диалогов настроек и первого запуска)
|
||||||
CONFIG_DIALOG_SIZE: Tuple[int, int] = (420, 540)
|
CONFIG_DIALOG_SIZE: Tuple[int, int] = (460, 560)
|
||||||
CONFIG_DIALOG_FRAME_PAD: Tuple[int, int] = (24, 20)
|
CONFIG_DIALOG_FRAME_PAD: Tuple[int, int] = (20, 14)
|
||||||
FIRST_RUN_SIZE: Tuple[int, int] = (520, 440)
|
FIRST_RUN_SIZE: Tuple[int, int] = (520, 440)
|
||||||
FIRST_RUN_FRAME_PAD: Tuple[int, int] = (28, 24)
|
FIRST_RUN_FRAME_PAD: Tuple[int, int] = (28, 24)
|
||||||
|
|
||||||
|
|
@ -62,6 +85,7 @@ def create_ctk_root(
|
||||||
Создаёт CTk: глобальная тема, заголовок, без ресайза, по центру экрана, фон из палитры.
|
Создаёт CTk: глобальная тема, заголовок, без ресайза, по центру экрана, фон из палитры.
|
||||||
after_create — опционально: установка иконки окна (различается по ОС).
|
after_create — опционально: установка иконки окна (различается по ОС).
|
||||||
"""
|
"""
|
||||||
|
_install_tkinter_variable_del_guard()
|
||||||
apply_ctk_appearance(ctk)
|
apply_ctk_appearance(ctk)
|
||||||
root = ctk.CTk()
|
root = ctk.CTk()
|
||||||
root.title(title)
|
root.title(title)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||||
|
from proxy import __version__
|
||||||
|
|
||||||
from ui.ctk_theme import (
|
from ui.ctk_theme import (
|
||||||
FIRST_RUN_FRAME_PAD,
|
FIRST_RUN_FRAME_PAD,
|
||||||
|
|
@ -54,6 +55,62 @@ _TIP_AUTOSTART = (
|
||||||
_TIP_SAVE = "Сохранить настройки в файл. После сохранения можно перезапустить прокси."
|
_TIP_SAVE = "Сохранить настройки в файл. После сохранения можно перезапустить прокси."
|
||||||
_TIP_CANCEL = "Закрыть окно без сохранения изменений."
|
_TIP_CANCEL = "Закрыть окно без сохранения изменений."
|
||||||
|
|
||||||
|
# Внутренняя ширина полей относительно ширины окна настроек (см. CONFIG_DIALOG_SIZE)
|
||||||
|
_CONFIG_FORM_INNER_WIDTH = 396
|
||||||
|
|
||||||
|
|
||||||
|
def tray_settings_scroll_and_footer(
|
||||||
|
ctk: Any,
|
||||||
|
content_parent: Any,
|
||||||
|
theme: CtkTheme,
|
||||||
|
) -> Tuple[Any, Any]:
|
||||||
|
"""
|
||||||
|
Нижняя панель под кнопки и прокручиваемая область для формы (форма не обрезает кнопки).
|
||||||
|
Возвращает (scroll_frame, footer_frame).
|
||||||
|
"""
|
||||||
|
footer = ctk.CTkFrame(content_parent, fg_color=theme.bg)
|
||||||
|
footer.pack(side="bottom", fill="x")
|
||||||
|
scroll = ctk.CTkScrollableFrame(
|
||||||
|
content_parent,
|
||||||
|
fg_color=theme.bg,
|
||||||
|
corner_radius=0,
|
||||||
|
scrollbar_button_color=theme.field_border,
|
||||||
|
scrollbar_button_hover_color=theme.text_secondary,
|
||||||
|
)
|
||||||
|
scroll.pack(fill="both", expand=True)
|
||||||
|
return scroll, footer
|
||||||
|
|
||||||
|
|
||||||
|
def _config_section(
|
||||||
|
ctk: Any,
|
||||||
|
parent: Any,
|
||||||
|
theme: CtkTheme,
|
||||||
|
title: str,
|
||||||
|
*,
|
||||||
|
bottom_spacer: int = 6,
|
||||||
|
) -> Any:
|
||||||
|
"""Заголовок секции и карточка с рамкой для группировки полей."""
|
||||||
|
wrap = ctk.CTkFrame(parent, fg_color="transparent")
|
||||||
|
wrap.pack(fill="x", pady=(0, bottom_spacer))
|
||||||
|
ctk.CTkLabel(
|
||||||
|
wrap,
|
||||||
|
text=title,
|
||||||
|
font=(theme.ui_font_family, 12, "bold"),
|
||||||
|
text_color=theme.text_primary,
|
||||||
|
anchor="w",
|
||||||
|
).pack(anchor="w", pady=(0, 2))
|
||||||
|
card = ctk.CTkFrame(
|
||||||
|
wrap,
|
||||||
|
fg_color=theme.field_bg,
|
||||||
|
corner_radius=10,
|
||||||
|
border_width=1,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
)
|
||||||
|
card.pack(fill="x")
|
||||||
|
inner = ctk.CTkFrame(card, fg_color="transparent")
|
||||||
|
inner.pack(fill="x", padx=10, pady=8)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TrayConfigFormWidgets:
|
class TrayConfigFormWidgets:
|
||||||
|
|
@ -77,80 +134,158 @@ def install_tray_config_form(
|
||||||
autostart_value: bool = False,
|
autostart_value: bool = False,
|
||||||
) -> TrayConfigFormWidgets:
|
) -> TrayConfigFormWidgets:
|
||||||
"""Поля настроек прокси внутри уже созданного `frame`."""
|
"""Поля настроек прокси внутри уже созданного `frame`."""
|
||||||
host_lbl = ctk.CTkLabel(frame, text="IP-адрес прокси",
|
header = ctk.CTkFrame(frame, fg_color="transparent")
|
||||||
font=(theme.ui_font_family, 13),
|
header.pack(fill="x", pady=(0, 2))
|
||||||
text_color=theme.text_primary, anchor="w")
|
ctk.CTkLabel(
|
||||||
host_lbl.pack(anchor="w", pady=(0, 4))
|
header,
|
||||||
|
text="Настройки прокси",
|
||||||
|
font=(theme.ui_font_family, 17, "bold"),
|
||||||
|
text_color=theme.text_primary,
|
||||||
|
anchor="w",
|
||||||
|
).pack(side="left")
|
||||||
|
ctk.CTkLabel(
|
||||||
|
header,
|
||||||
|
text=f"v{__version__}",
|
||||||
|
font=(theme.ui_font_family, 12),
|
||||||
|
text_color=theme.text_secondary,
|
||||||
|
anchor="e",
|
||||||
|
).pack(side="right")
|
||||||
|
|
||||||
|
inner_w = _CONFIG_FORM_INNER_WIDTH
|
||||||
|
|
||||||
|
conn = _config_section(ctk, frame, theme, "Подключение SOCKS5")
|
||||||
|
|
||||||
|
host_row = ctk.CTkFrame(conn, fg_color="transparent")
|
||||||
|
host_row.pack(fill="x")
|
||||||
|
|
||||||
|
host_col = ctk.CTkFrame(host_row, fg_color="transparent")
|
||||||
|
host_col.pack(side="left", fill="x", expand=True, padx=(0, 10))
|
||||||
|
host_lbl = ctk.CTkLabel(
|
||||||
|
host_col,
|
||||||
|
text="IP-адрес",
|
||||||
|
font=(theme.ui_font_family, 12),
|
||||||
|
text_color=theme.text_secondary,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
host_lbl.pack(anchor="w", pady=(0, 2))
|
||||||
host_var = ctk.StringVar(value=cfg.get("host", default_config["host"]))
|
host_var = ctk.StringVar(value=cfg.get("host", default_config["host"]))
|
||||||
host_entry = ctk.CTkEntry(
|
host_entry = ctk.CTkEntry(
|
||||||
frame, textvariable=host_var, width=200, height=36,
|
host_col,
|
||||||
font=(theme.ui_font_family, 13), corner_radius=10,
|
textvariable=host_var,
|
||||||
fg_color=theme.field_bg, border_color=theme.field_border,
|
width=160,
|
||||||
border_width=1, text_color=theme.text_primary)
|
height=36,
|
||||||
host_entry.pack(anchor="w", pady=(0, 12))
|
|
||||||
attach_tooltip_to_widgets([host_lbl, host_entry], _TIP_HOST)
|
|
||||||
|
|
||||||
port_lbl = ctk.CTkLabel(frame, text="Порт прокси",
|
|
||||||
font=(theme.ui_font_family, 13),
|
font=(theme.ui_font_family, 13),
|
||||||
text_color=theme.text_primary, anchor="w")
|
corner_radius=10,
|
||||||
port_lbl.pack(anchor="w", pady=(0, 4))
|
fg_color=theme.bg,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
border_width=1,
|
||||||
|
text_color=theme.text_primary,
|
||||||
|
)
|
||||||
|
host_entry.pack(fill="x", pady=(0, 0))
|
||||||
|
attach_tooltip_to_widgets([host_lbl, host_entry, host_col], _TIP_HOST)
|
||||||
|
|
||||||
|
port_col = ctk.CTkFrame(host_row, fg_color="transparent")
|
||||||
|
port_col.pack(side="left")
|
||||||
|
port_lbl = ctk.CTkLabel(
|
||||||
|
port_col,
|
||||||
|
text="Порт",
|
||||||
|
font=(theme.ui_font_family, 12),
|
||||||
|
text_color=theme.text_secondary,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
port_lbl.pack(anchor="w", pady=(0, 2))
|
||||||
port_var = ctk.StringVar(value=str(cfg.get("port", default_config["port"])))
|
port_var = ctk.StringVar(value=str(cfg.get("port", default_config["port"])))
|
||||||
port_entry = ctk.CTkEntry(
|
port_entry = ctk.CTkEntry(
|
||||||
frame, textvariable=port_var, width=120, height=36,
|
port_col,
|
||||||
font=(theme.ui_font_family, 13), corner_radius=10,
|
textvariable=port_var,
|
||||||
fg_color=theme.field_bg, border_color=theme.field_border,
|
width=100,
|
||||||
border_width=1, text_color=theme.text_primary)
|
height=36,
|
||||||
port_entry.pack(anchor="w", pady=(0, 12))
|
font=(theme.ui_font_family, 13),
|
||||||
attach_tooltip_to_widgets([port_lbl, port_entry], _TIP_PORT)
|
corner_radius=10,
|
||||||
|
fg_color=theme.bg,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
border_width=1,
|
||||||
|
text_color=theme.text_primary,
|
||||||
|
)
|
||||||
|
port_entry.pack(anchor="w")
|
||||||
|
attach_tooltip_to_widgets([port_lbl, port_entry, port_col], _TIP_PORT)
|
||||||
|
|
||||||
|
dc_inner = _config_section(ctk, frame, theme, "Датацентры Telegram (DC → IP)")
|
||||||
dc_lbl = ctk.CTkLabel(
|
dc_lbl = ctk.CTkLabel(
|
||||||
frame, text="DC → IP маппинги (по одному на строку, формат DC:IP)",
|
dc_inner,
|
||||||
font=(theme.ui_font_family, 13), text_color=theme.text_primary,
|
text="По одному правилу на строку, формат: номер:IP",
|
||||||
anchor="w")
|
font=(theme.ui_font_family, 11),
|
||||||
|
text_color=theme.text_secondary,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
dc_lbl.pack(anchor="w", pady=(0, 4))
|
dc_lbl.pack(anchor="w", pady=(0, 4))
|
||||||
dc_textbox = ctk.CTkTextbox(
|
dc_textbox = ctk.CTkTextbox(
|
||||||
frame, width=370, height=120,
|
dc_inner,
|
||||||
font=(theme.mono_font_family, 12), corner_radius=10,
|
width=inner_w,
|
||||||
fg_color=theme.field_bg, border_color=theme.field_border,
|
height=88,
|
||||||
border_width=1, text_color=theme.text_primary)
|
font=(theme.mono_font_family, 12),
|
||||||
dc_textbox.pack(anchor="w", pady=(0, 12))
|
corner_radius=10,
|
||||||
|
fg_color=theme.bg,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
border_width=1,
|
||||||
|
text_color=theme.text_primary,
|
||||||
|
)
|
||||||
|
dc_textbox.pack(fill="x")
|
||||||
dc_textbox.insert("1.0", "\n".join(cfg.get("dc_ip", default_config["dc_ip"])))
|
dc_textbox.insert("1.0", "\n".join(cfg.get("dc_ip", default_config["dc_ip"])))
|
||||||
attach_tooltip_to_widgets([dc_lbl, dc_textbox], _TIP_DC)
|
attach_tooltip_to_widgets([dc_lbl, dc_textbox], _TIP_DC)
|
||||||
|
|
||||||
|
log_inner = _config_section(ctk, frame, theme, "Логи и производительность")
|
||||||
|
|
||||||
verbose_var = ctk.BooleanVar(value=cfg.get("verbose", False))
|
verbose_var = ctk.BooleanVar(value=cfg.get("verbose", False))
|
||||||
verbose_cb = ctk.CTkCheckBox(
|
verbose_cb = ctk.CTkCheckBox(
|
||||||
frame, text="Подробное логирование (verbose)",
|
log_inner,
|
||||||
variable=verbose_var, font=(theme.ui_font_family, 13),
|
text="Подробное логирование (verbose)",
|
||||||
|
variable=verbose_var,
|
||||||
|
font=(theme.ui_font_family, 13),
|
||||||
text_color=theme.text_primary,
|
text_color=theme.text_primary,
|
||||||
fg_color=theme.tg_blue, hover_color=theme.tg_blue_hover,
|
fg_color=theme.tg_blue,
|
||||||
corner_radius=6, border_width=2,
|
hover_color=theme.tg_blue_hover,
|
||||||
border_color=theme.field_border)
|
corner_radius=6,
|
||||||
verbose_cb.pack(anchor="w", pady=(0, 8))
|
border_width=2,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
)
|
||||||
|
verbose_cb.pack(anchor="w", pady=(0, 6))
|
||||||
attach_ctk_tooltip(verbose_cb, _TIP_VERBOSE)
|
attach_ctk_tooltip(verbose_cb, _TIP_VERBOSE)
|
||||||
|
|
||||||
adv_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
adv_frame = ctk.CTkFrame(log_inner, fg_color="transparent")
|
||||||
adv_frame.pack(anchor="w", fill="x", pady=(4, 8))
|
adv_frame.pack(fill="x")
|
||||||
|
|
||||||
adv_rows = [
|
adv_rows = [
|
||||||
("Буфер (KB, 256 default)", "buf_kb", 120, _TIP_BUF_KB),
|
("Буфер, КБ (по умолчанию 256)", "buf_kb", _TIP_BUF_KB),
|
||||||
("WS пулов (4 default)", "pool_size", 120, _TIP_POOL),
|
("Пул WebSocket-сессий (по умолчанию 4)", "pool_size", _TIP_POOL),
|
||||||
("Log size (MB, 5 def)", "log_max_mb", 120, _TIP_LOG_MB),
|
("Макс. размер лога, МБ (по умолчанию 5)", "log_max_mb", _TIP_LOG_MB),
|
||||||
]
|
]
|
||||||
for lbl, key, w_, tip in adv_rows:
|
for lbl, key, tip in adv_rows:
|
||||||
col_frame = ctk.CTkFrame(adv_frame, fg_color="transparent")
|
col_frame = ctk.CTkFrame(adv_frame, fg_color="transparent")
|
||||||
col_frame.pack(side="left", padx=(0, 10))
|
col_frame.pack(fill="x", pady=(0, 0 if key == "log_max_mb" else 5))
|
||||||
adv_l = ctk.CTkLabel(col_frame, text=lbl, font=(theme.ui_font_family, 11),
|
adv_l = ctk.CTkLabel(
|
||||||
text_color=theme.text_secondary, anchor="w")
|
col_frame,
|
||||||
adv_l.pack(anchor="w")
|
text=lbl,
|
||||||
|
font=(theme.ui_font_family, 11),
|
||||||
|
text_color=theme.text_secondary,
|
||||||
|
anchor="w",
|
||||||
|
)
|
||||||
|
adv_l.pack(anchor="w", pady=(0, 2))
|
||||||
adv_e = ctk.CTkEntry(
|
adv_e = ctk.CTkEntry(
|
||||||
col_frame, width=w_, height=30, font=(theme.ui_font_family, 12),
|
col_frame,
|
||||||
corner_radius=8, fg_color=theme.field_bg,
|
width=inner_w,
|
||||||
border_color=theme.field_border, border_width=1,
|
height=32,
|
||||||
|
font=(theme.ui_font_family, 13),
|
||||||
|
corner_radius=8,
|
||||||
|
fg_color=theme.bg,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
border_width=1,
|
||||||
text_color=theme.text_primary,
|
text_color=theme.text_primary,
|
||||||
textvariable=ctk.StringVar(
|
textvariable=ctk.StringVar(
|
||||||
value=str(cfg.get(key, default_config[key]))
|
value=str(cfg.get(key, default_config[key]))
|
||||||
))
|
),
|
||||||
adv_e.pack(anchor="w")
|
)
|
||||||
|
adv_e.pack(fill="x")
|
||||||
attach_tooltip_to_widgets([adv_l, adv_e, col_frame], tip)
|
attach_tooltip_to_widgets([adv_l, adv_e, col_frame], tip)
|
||||||
|
|
||||||
adv_entries = list(adv_frame.winfo_children())
|
adv_entries = list(adv_frame.winfo_children())
|
||||||
|
|
@ -158,21 +293,33 @@ def install_tray_config_form(
|
||||||
|
|
||||||
autostart_var = None
|
autostart_var = None
|
||||||
if show_autostart:
|
if show_autostart:
|
||||||
|
sys_inner = _config_section(
|
||||||
|
ctk, frame, theme, "Запуск Windows", bottom_spacer=4
|
||||||
|
)
|
||||||
autostart_var = ctk.BooleanVar(value=autostart_value)
|
autostart_var = ctk.BooleanVar(value=autostart_value)
|
||||||
as_cb = ctk.CTkCheckBox(
|
as_cb = ctk.CTkCheckBox(
|
||||||
frame, text="Автозапуск при включении Windows",
|
sys_inner,
|
||||||
variable=autostart_var, font=(theme.ui_font_family, 13),
|
text="Автозапуск при включении компьютера",
|
||||||
|
variable=autostart_var,
|
||||||
|
font=(theme.ui_font_family, 13),
|
||||||
text_color=theme.text_primary,
|
text_color=theme.text_primary,
|
||||||
fg_color=theme.tg_blue, hover_color=theme.tg_blue_hover,
|
fg_color=theme.tg_blue,
|
||||||
corner_radius=6, border_width=2,
|
hover_color=theme.tg_blue_hover,
|
||||||
border_color=theme.field_border)
|
corner_radius=6,
|
||||||
as_cb.pack(anchor="w", pady=(0, 8))
|
border_width=2,
|
||||||
|
border_color=theme.field_border,
|
||||||
|
)
|
||||||
|
as_cb.pack(anchor="w", pady=(0, 4))
|
||||||
as_hint = ctk.CTkLabel(
|
as_hint = ctk.CTkLabel(
|
||||||
frame, text="При перемещении файла или открытии из другой папки\n"
|
sys_inner,
|
||||||
"автозапуск будет сброшен",
|
text="Если переместить программу в другую папку, запись автозапуска может сброситься.",
|
||||||
font=(theme.ui_font_family, 13), text_color=theme.text_secondary,
|
font=(theme.ui_font_family, 11),
|
||||||
anchor="w", justify="left")
|
text_color=theme.text_secondary,
|
||||||
as_hint.pack(anchor="w", pady=(0, 8))
|
anchor="w",
|
||||||
|
justify="left",
|
||||||
|
wraplength=inner_w,
|
||||||
|
)
|
||||||
|
as_hint.pack(anchor="w")
|
||||||
attach_tooltip_to_widgets([as_cb, as_hint], _TIP_AUTOSTART)
|
attach_tooltip_to_widgets([as_cb, as_hint], _TIP_AUTOSTART)
|
||||||
|
|
||||||
return TrayConfigFormWidgets(
|
return TrayConfigFormWidgets(
|
||||||
|
|
@ -263,8 +410,14 @@ def install_tray_config_buttons(
|
||||||
on_save: Callable[[], None],
|
on_save: Callable[[], None],
|
||||||
on_cancel: Callable[[], None],
|
on_cancel: Callable[[], None],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
ctk.CTkFrame(
|
||||||
|
frame,
|
||||||
|
fg_color=theme.field_border,
|
||||||
|
height=1,
|
||||||
|
corner_radius=0,
|
||||||
|
).pack(fill="x", pady=(4, 10))
|
||||||
btn_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
btn_frame = ctk.CTkFrame(frame, fg_color="transparent")
|
||||||
btn_frame.pack(fill="x", pady=(20, 0))
|
btn_frame.pack(fill="x", pady=(0, 0))
|
||||||
save_btn = ctk.CTkButton(
|
save_btn = ctk.CTkButton(
|
||||||
btn_frame, text="Сохранить", height=38,
|
btn_frame, text="Сохранить", height=38,
|
||||||
font=(theme.ui_font_family, 14, "bold"), corner_radius=10,
|
font=(theme.ui_font_family, 14, "bold"), corner_radius=10,
|
||||||
|
|
|
||||||
28
windows.py
28
windows.py
|
|
@ -37,10 +37,12 @@ except ImportError:
|
||||||
Image = ImageDraw = ImageFont = None
|
Image = ImageDraw = ImageFont = None
|
||||||
|
|
||||||
import proxy.tg_ws_proxy as tg_ws_proxy
|
import proxy.tg_ws_proxy as tg_ws_proxy
|
||||||
|
from proxy import __version__
|
||||||
from ui.ctk_tray_ui import (
|
from ui.ctk_tray_ui import (
|
||||||
install_tray_config_buttons,
|
install_tray_config_buttons,
|
||||||
install_tray_config_form,
|
install_tray_config_form,
|
||||||
populate_first_run_window,
|
populate_first_run_window,
|
||||||
|
tray_settings_scroll_and_footer,
|
||||||
validate_config_form,
|
validate_config_form,
|
||||||
)
|
)
|
||||||
from ui.ctk_theme import (
|
from ui.ctk_theme import (
|
||||||
|
|
@ -453,7 +455,7 @@ def _edit_config_dialog():
|
||||||
theme = ctk_theme_for_platform()
|
theme = ctk_theme_for_platform()
|
||||||
w, h = CONFIG_DIALOG_SIZE
|
w, h = CONFIG_DIALOG_SIZE
|
||||||
if _supports_autostart():
|
if _supports_autostart():
|
||||||
h += 70
|
h += 100
|
||||||
|
|
||||||
icon_path = str(Path(__file__).parent / "icon.ico")
|
icon_path = str(Path(__file__).parent / "icon.ico")
|
||||||
|
|
||||||
|
|
@ -469,9 +471,11 @@ def _edit_config_dialog():
|
||||||
fpx, fpy = CONFIG_DIALOG_FRAME_PAD
|
fpx, fpy = CONFIG_DIALOG_FRAME_PAD
|
||||||
frame = main_content_frame(ctk, root, theme, padx=fpx, pady=fpy)
|
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(
|
widgets = install_tray_config_form(
|
||||||
ctk,
|
ctk,
|
||||||
frame,
|
scroll,
|
||||||
theme,
|
theme,
|
||||||
cfg,
|
cfg,
|
||||||
DEFAULT_CONFIG,
|
DEFAULT_CONFIG,
|
||||||
|
|
@ -515,9 +519,17 @@ def _edit_config_dialog():
|
||||||
root.destroy()
|
root.destroy()
|
||||||
|
|
||||||
install_tray_config_buttons(
|
install_tray_config_buttons(
|
||||||
ctk, frame, theme, on_save=on_save, on_cancel=on_cancel)
|
ctk, footer, theme, on_save=on_save, on_cancel=on_cancel)
|
||||||
|
|
||||||
|
try:
|
||||||
root.mainloop()
|
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):
|
def _on_open_logs(icon=None, item=None):
|
||||||
|
|
@ -579,7 +591,15 @@ def _show_first_run():
|
||||||
populate_first_run_window(
|
populate_first_run_window(
|
||||||
ctk, root, theme, host=host, port=port, on_done=on_done)
|
ctk, root, theme, host=host, port=port, on_done=on_done)
|
||||||
|
|
||||||
|
try:
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
finally:
|
||||||
|
import tkinter as tk
|
||||||
|
try:
|
||||||
|
if root.winfo_exists():
|
||||||
|
root.destroy()
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _has_ipv6_enabled() -> bool:
|
def _has_ipv6_enabled() -> bool:
|
||||||
|
|
@ -667,7 +687,7 @@ def run_tray():
|
||||||
|
|
||||||
setup_logging(_config.get("verbose", False),
|
setup_logging(_config.get("verbose", False),
|
||||||
log_max_mb=_config.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"]))
|
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("Config: %s", _config)
|
||||||
log.info("Log file: %s", LOG_FILE)
|
log.info("Log file: %s", LOG_FILE)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue