diff --git a/macos.py b/macos.py index bb22a21..7c0f7d0 100644 --- a/macos.py +++ b/macos.py @@ -125,29 +125,63 @@ def _activate_app() -> None: log.warning("Failed to activate macOS app: %s", repr(exc)) +def _hide_ctk_root() -> None: + if _ctk_root is None: + return + try: + _ctk_root.withdraw() + except Exception as exc: + log.warning("Failed to hide CTk root: %s", repr(exc)) + + def _dispatch(callback: Callable[[], None]) -> None: if _ctk_root is None: return - if threading.current_thread() is threading.main_thread(): - callback() - return + + def invoke() -> None: + try: + callback() + except Exception as exc: + log.exception("UI callback failed") + try: + _show_error(str(exc)) + except Exception as dialog_exc: + log.error("Failed to show UI error: %s", repr(dialog_exc)) + try: - _ctk_root.after(0, callback) + _ctk_root.after_idle(invoke) except Exception as exc: log.warning("Failed to dispatch UI callback: %s", repr(exc)) def _messagebox(kind: str, text: str, title: str) -> Any: + import tkinter as tk from tkinter import messagebox result: list[Any] = [] done = threading.Event() def show() -> None: + parent = None try: _activate_app() - result.append(getattr(messagebox, kind)(title, text, parent=_ctk_root)) + _hide_ctk_root() + parent = tk.Toplevel(_ctk_root) + parent.withdraw() + result.append(getattr(messagebox, kind)(title, text, parent=parent)) finally: + if parent is not None: + try: + parent.destroy() + except tk.TclError: + pass + _hide_ctk_root() + if _ctk_root is not None: + try: + _ctk_root.after_idle(_hide_ctk_root) + _ctk_root.after(50, _hide_ctk_root) + except tk.TclError: + pass done.set() if threading.current_thread() is threading.main_thread(): @@ -265,11 +299,16 @@ def _on_exit(icon=None, item=None) -> None: def _edit_config_dialog() -> None: global _settings_window - if _settings_window is not None and _settings_window.winfo_exists(): - _activate_app() - _settings_window.lift() - _settings_window.focus_force() - return + if _settings_window is not None: + try: + if _settings_window.winfo_exists(): + _activate_app() + _settings_window.lift() + _settings_window.focus_force() + return + except Exception as exc: + log.warning("Failed to reuse settings window: %s", repr(exc)) + _settings_window = None cfg = dict(_config) theme = ctk_theme_for_platform() @@ -312,6 +351,7 @@ def _edit_config_dialog() -> None: global _settings_window root.destroy() _settings_window = None + _hide_ctk_root() def cancel() -> None: ctk.set_appearance_mode(original_appearance) @@ -404,6 +444,7 @@ def _show_first_run() -> None: def done(open_telegram: bool) -> None: FIRST_RUN_MARKER.touch() root.destroy() + _hide_ctk_root() if open_telegram: _on_open_in_telegram() check_ipv6_warning(_show_info) @@ -450,6 +491,12 @@ def _initialize_gui() -> bool: install_tkinter_variable_del_guard() apply_ctk_appearance(ctk, _config.get("appearance", "auto")) _ctk_root = ctk.CTk() + _ctk_root.title(t("app.name")) + _ctk_root.geometry("1x1+0+0") + try: + _ctk_root.attributes("-alpha", 0.0) + except Exception as exc: + log.warning("Failed to make CTk root transparent: %s", repr(exc)) _ctk_root.withdraw() _ns_app = NSApplication.sharedApplication() if NSApplicationActivationPolicyAccessory is not None: