diff --git a/macos.py b/macos.py index 7c0f7d0..785af35 100644 --- a/macos.py +++ b/macos.py @@ -1,5 +1,6 @@ from __future__ import annotations +import faulthandler import subprocess import sys import threading @@ -89,6 +90,7 @@ from ui.ctk_tray_ui import ( ) from ui.i18n import set_language, t from utils.tray_common import ( + APP_DIR, APP_NAME, DEFAULT_CONFIG, FIRST_RUN_MARKER, @@ -115,6 +117,7 @@ _settings_window: Optional[Any] = None _ns_app: Optional[Any] = None _config: dict = {} _exiting = False +_crash_log: Optional[Any] = None def _activate_app() -> None: @@ -134,7 +137,7 @@ def _hide_ctk_root() -> None: log.warning("Failed to hide CTk root: %s", repr(exc)) -def _dispatch(callback: Callable[[], None]) -> None: +def _dispatch(callback: Callable[[], None], delay_ms: int = 0) -> None: if _ctk_root is None: return @@ -149,7 +152,10 @@ def _dispatch(callback: Callable[[], None]) -> None: log.error("Failed to show UI error: %s", repr(dialog_exc)) try: - _ctk_root.after_idle(invoke) + if delay_ms > 0: + _ctk_root.after(delay_ms, invoke) + else: + _ctk_root.after_idle(invoke) except Exception as exc: log.warning("Failed to dispatch UI callback: %s", repr(exc)) @@ -265,7 +271,8 @@ def _on_restart(icon=None, item=None) -> None: def _on_edit_config(icon=None, item=None) -> None: - _dispatch(_edit_config_dialog) + log.info("Settings requested") + _dispatch(_edit_config_dialog, delay_ms=300) def _on_open_logs(icon=None, item=None) -> None: @@ -310,6 +317,7 @@ def _edit_config_dialog() -> None: log.warning("Failed to reuse settings window: %s", repr(exc)) _settings_window = None + log.info("Creating settings window") cfg = dict(_config) theme = ctk_theme_for_platform() width, height = CONFIG_DIALOG_SIZE @@ -322,6 +330,7 @@ def _edit_config_dialog() -> None: after_create=lambda window: _activate_app(), ) _settings_window = root + log.info("Settings window created") frame_pad_x, frame_pad_y = CONFIG_DIALOG_FRAME_PAD frame = main_content_frame( ctk, @@ -332,6 +341,7 @@ def _edit_config_dialog() -> None: ) scroll, footer = tray_settings_scroll_and_footer(ctk, frame, theme) original_language = _config.get("language", DEFAULT_CONFIG["language"]) + log.info("Building settings form") widgets = install_tray_config_form( ctk, scroll, @@ -341,6 +351,7 @@ def _edit_config_dialog() -> None: show_autostart=False, on_language_change=_refresh_tray_menu, ) + log.info("Settings form built") original_appearance = ctk.get_appearance_mode() def restore_ui_locale() -> None: @@ -419,6 +430,7 @@ def _edit_config_dialog() -> None: on_cancel=cancel, ) _activate_app() + log.info("Settings window ready") def _show_first_run() -> None: @@ -504,10 +516,21 @@ def _initialize_gui() -> bool: return True +def _enable_crash_log() -> None: + global _crash_log + try: + ensure_dirs() + _crash_log = open(APP_DIR / "crash.log", "a", encoding="utf-8", buffering=1) + faulthandler.enable(file=_crash_log, all_threads=True) + except OSError as exc: + log.warning("Failed to enable crash log: %s", repr(exc)) + + def run_tray() -> None: global _tray_icon, _config _config = load_config() bootstrap(_config) + _enable_crash_log() if not _initialize_gui(): log.error("pystray, Pillow, customtkinter or AppKit not installed; running in console mode")