diff --git a/ClipCascade_Desktop/src/.gitignore b/ClipCascade_Desktop/src/.gitignore index 1cc8aa82..79cd2dad 100644 --- a/ClipCascade_Desktop/src/.gitignore +++ b/ClipCascade_Desktop/src/.gitignore @@ -1,3 +1,7 @@ **/__pycache__/ *.log DATA +build/ +dist/ +*.spec.bak +.venv/ diff --git a/ClipCascade_Desktop/src/ClipCascade_macos.spec b/ClipCascade_Desktop/src/ClipCascade_macos.spec new file mode 100644 index 00000000..093361e4 --- /dev/null +++ b/ClipCascade_Desktop/src/ClipCascade_macos.spec @@ -0,0 +1,49 @@ +# -*- mode: python ; coding: utf-8 -*- + +import certifi + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[(certifi.where(), 'certifi')], + hiddenimports=['tkinter'], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='ClipCascade', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['../../logo/logo.icns'], +) +app = BUNDLE( + exe, + name='ClipCascade.app', + icon='../../logo/logo.icns', + bundle_identifier=None, + info_plist={ + 'LSUIElement': True, + }, +) diff --git a/ClipCascade_Desktop/src/gui/tray.py b/ClipCascade_Desktop/src/gui/tray.py index ccd43a7e..c35c4c05 100644 --- a/ClipCascade_Desktop/src/gui/tray.py +++ b/ClipCascade_Desktop/src/gui/tray.py @@ -47,6 +47,14 @@ def __init__( self.root = tk.Tk() self.root.withdraw() # Hide the root window + # Hide dock icon on macOS after creating tkinter window + if PLATFORM == MACOS: + try: + from AppKit import NSApplication, NSApplicationActivationPolicyAccessory + NSApplication.sharedApplication().setActivationPolicy_(NSApplicationActivationPolicyAccessory) + except ImportError: + pass + # Initial state: Connected self.is_connected = True diff --git a/ClipCascade_Desktop/src/main.py b/ClipCascade_Desktop/src/main.py index dada6eaf..6116cc2a 100644 --- a/ClipCascade_Desktop/src/main.py +++ b/ClipCascade_Desktop/src/main.py @@ -9,13 +9,42 @@ # This script serves as the entry point for the ClipCascade application, # initializing and running the core application logic. -from core.application import Application +import sys +import platform -class Main: - def __init__(self): - Application().run() +def show_error_dialog(title, message): + """Show native error dialog using osascript (macOS) or fallback to console.""" + if platform.system() == "Darwin": + import subprocess + + def sanitize(text): + return (text + .replace("\\", "") + .replace('"', "'") + .replace("\n", " ") + .replace("\r", " ") + .replace("\t", " ")) + + safe_msg = sanitize(message) + safe_title = sanitize(title) + script = f'display dialog "{safe_msg}" with title "{safe_title}" buttons {{"OK"}} default button 1 with icon stop' + try: + subprocess.run(["osascript", "-e", script], check=False) + except Exception: + pass + print(f"{title}: {message}", file=sys.stderr) if __name__ == "__main__": - Main() + try: + from core.application import Application + + Application().run() + except Exception as e: + import traceback + + error_msg = f"{type(e).__name__}: {e}" + show_error_dialog("ClipCascade Error", error_msg) + traceback.print_exc() + sys.exit(1) diff --git a/ClipCascade_Desktop/src/requirements_mac.txt b/ClipCascade_Desktop/src/requirements_mac.txt index 494bc5de..715460a0 100644 --- a/ClipCascade_Desktop/src/requirements_mac.txt +++ b/ClipCascade_Desktop/src/requirements_mac.txt @@ -10,3 +10,4 @@ websocket_client==1.8.0 xxhash==3.5.0 beautifulsoup4==4.12.3 aiortc==1.10.0 +pyinstaller==6.17.0