feat: add manual update triggers to settings and tray menu (#1213)

This commit is contained in:
Konukhov Yaroslav
2026-09-04 17:13:56 +03:00
committed by GitHub
parent bd4f3d080c
commit d36bb7df71
4 changed files with 80 additions and 27 deletions
+30 -8
View File
@@ -373,6 +373,7 @@ def install_tray_config_form(
show_autostart: bool = False, show_autostart: bool = False,
autostart_value: bool = False, autostart_value: bool = False,
on_language_change: Optional[Callable[[], None]] = None, on_language_change: Optional[Callable[[], None]] = None,
on_update_click: Optional[Callable[[], None]] = None,
) -> TrayConfigFormWidgets: ) -> TrayConfigFormWidgets:
lang_cfg = cfg.get("language", default_config["language"]) lang_cfg = cfg.get("language", default_config["language"])
set_language(lang_cfg) set_language(lang_cfg)
@@ -776,14 +777,35 @@ def install_tray_config_form(
justify="left", wraplength=_INNER_W).pack(anchor="w", pady=(0, 8)) justify="left", wraplength=_INNER_W).pack(anchor="w", pady=(0, 8))
rel_url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL rel_url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL
ctk.CTkButton( if st.get("has_update") and on_update_click is not None:
upd_inner, text=t("button.open_release"), height=32, upd_btn_row = ctk.CTkFrame(upd_inner, fg_color="transparent")
font=(theme.ui_font_family, 13), corner_radius=8, upd_btn_row.pack(fill="x")
fg_color=theme.field_bg, hover_color=theme.field_border, upd_btn_row.grid_columnconfigure(0, weight=1)
text_color=theme.text_primary, border_width=1, upd_btn_row.grid_columnconfigure(1, weight=1)
border_color=theme.field_border, ctk.CTkButton(
command=lambda u=rel_url: webbrowser.open(u), upd_btn_row, text=t("button.open_release"), height=32,
).pack(anchor="w") font=(theme.ui_font_family, 13), corner_radius=8,
fg_color=theme.field_bg, hover_color=theme.field_border,
text_color=theme.text_primary, border_width=1,
border_color=theme.field_border,
command=lambda u=rel_url: webbrowser.open(u),
).grid(row=0, column=0, sticky="ew", padx=(0, 4))
ctk.CTkButton(
upd_btn_row, text=t("button.update"), height=32,
font=(theme.ui_font_family, 13, "bold"), corner_radius=8,
fg_color=theme.tg_blue, hover_color=theme.tg_blue_hover,
text_color="#ffffff",
command=on_update_click,
).grid(row=0, column=1, sticky="ew", padx=(4, 0))
else:
ctk.CTkButton(
upd_inner, text=t("button.open_release"), height=32,
font=(theme.ui_font_family, 13), corner_radius=8,
fg_color=theme.field_bg, hover_color=theme.field_border,
text_color=theme.text_primary, border_width=1,
border_color=theme.field_border,
command=lambda u=rel_url: webbrowser.open(u),
).pack(anchor="w")
autostart_var = None autostart_var = None
if show_autostart: if show_autostart:
+1
View File
@@ -113,6 +113,7 @@
"tray.restart": "Restart proxy", "tray.restart": "Restart proxy",
"tray.settings": "Settings...", "tray.settings": "Settings...",
"tray.logs": "Open logs", "tray.logs": "Open logs",
"tray.update": "Update ({current} → {new})",
"tray.exit": "Exit", "tray.exit": "Exit",
"dialog.restart_title": "Restart?", "dialog.restart_title": "Restart?",
+1
View File
@@ -113,6 +113,7 @@
"tray.restart": "Перезапустить прокси", "tray.restart": "Перезапустить прокси",
"tray.settings": "Настройки...", "tray.settings": "Настройки...",
"tray.logs": "Открыть логи", "tray.logs": "Открыть логи",
"tray.update": "Обновить ({current} → {new})",
"tray.exit": "Выход", "tray.exit": "Выход",
"dialog.restart_title": "Перезапустить?", "dialog.restart_title": "Перезапустить?",
+48 -19
View File
@@ -34,7 +34,7 @@ try:
except ImportError: except ImportError:
Image = None Image = None
from proxy import get_link_host from proxy import __version__, get_link_host
from utils.win32_theme import ( from utils.win32_theme import (
is_windows_dark_theme, is_windows_dark_theme,
@@ -317,6 +317,34 @@ def _perform_update(download_url: str, set_status=None) -> None:
os._exit(0) os._exit(0)
def _trigger_update_flow(icon=None, item=None) -> None:
"""Show the update dialog for an update already known to be available.
Reused by the tray "Update" item and the settings dialog's "Update"
button, so an update can still be started after the initial startup
prompt was skipped/closed.
"""
from utils.update_check import RELEASES_PAGE_URL, get_status, get_update_asset
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 "?"
asset = get_update_asset(Path(sys.executable), __version__) if IS_FROZEN else None
def _show() -> None:
choice = update_ctk_form(
t("update.available", version=ver),
download_url=asset[0] if asset else None,
release_url=url,
)
if choice == "open":
webbrowser.open(url)
threading.Thread(target=_show, daemon=True, name="manual-update").start()
def _maybe_do_update(cfg: dict, is_exiting) -> None: def _maybe_do_update(cfg: dict, is_exiting) -> None:
if not cfg.get("check_updates", True): if not cfg.get("check_updates", True):
return return
@@ -326,23 +354,14 @@ def _maybe_do_update(cfg: dict, is_exiting) -> None:
if is_exiting(): if is_exiting():
return return
try: try:
from proxy import __version__ from utils.update_check import run_check
from utils.update_check import RELEASES_PAGE_URL, get_status, get_update_asset, run_check
run_check(__version__) run_check(__version__)
st = get_status() if _tray_icon is not None:
if not st.get("has_update") or is_exiting(): _tray_icon.menu = _build_menu()
if is_exiting():
return return
url = (st.get("html_url") or "").strip() or RELEASES_PAGE_URL _trigger_update_flow()
ver = st.get("latest") or "?"
asset = get_update_asset(Path(sys.executable), __version__) if IS_FROZEN else None
choice = update_ctk_form(
t("update.available", version=ver),
download_url=asset[0] if asset else None,
release_url=url,
)
if choice == "open":
webbrowser.open(url)
except Exception as exc: except Exception as exc:
log.warning("Update check failed: %s", repr(exc)) log.warning("Update check failed: %s", repr(exc))
@@ -498,6 +517,7 @@ def _edit_config_dialog() -> None:
show_autostart=_supports_autostart(), show_autostart=_supports_autostart(),
autostart_value=cfg.get("autostart", False), autostart_value=cfg.get("autostart", False),
on_language_change=_refresh_tray_menu, on_language_change=_refresh_tray_menu,
on_update_click=_trigger_update_flow,
) )
_original_appearance = ctk.get_appearance_mode() _original_appearance = ctk.get_appearance_mode()
@@ -602,16 +622,25 @@ def _build_menu():
host = _config.get("host", DEFAULT_CONFIG["host"]) host = _config.get("host", DEFAULT_CONFIG["host"])
port = _config.get("port", DEFAULT_CONFIG["port"]) port = _config.get("port", DEFAULT_CONFIG["port"])
link_host = get_link_host(host) link_host = get_link_host(host)
return pystray.Menu( items = [
pystray.MenuItem(t("tray.open_telegram", host=link_host, port=port), _on_open_in_telegram, default=True), pystray.MenuItem(t("tray.open_telegram", host=link_host, port=port), _on_open_in_telegram, default=True),
pystray.MenuItem(t("tray.copy_link"), _on_copy_link), pystray.MenuItem(t("tray.copy_link"), _on_copy_link),
pystray.Menu.SEPARATOR, pystray.Menu.SEPARATOR,
pystray.MenuItem(t("tray.restart"), _on_restart), pystray.MenuItem(t("tray.restart"), _on_restart),
pystray.MenuItem(t("tray.settings"), _on_edit_config), pystray.MenuItem(t("tray.settings"), _on_edit_config),
pystray.MenuItem(t("tray.logs"), _on_open_logs), pystray.MenuItem(t("tray.logs"), _on_open_logs),
pystray.Menu.SEPARATOR, ]
pystray.MenuItem(t("tray.exit"), _on_exit), from utils.update_check import get_status
) st = get_status()
if st.get("has_update"):
items.append(pystray.Menu.SEPARATOR)
items.append(pystray.MenuItem(
t("tray.update", current=__version__, new=st.get("latest") or "?"),
_trigger_update_flow,
))
items.append(pystray.Menu.SEPARATOR)
items.append(pystray.MenuItem(t("tray.exit"), _on_exit))
return pystray.Menu(*items)
# entry point # entry point