""" modules/clipboard_sync.py — FRIDAY Cross-Device Clipboard Sync Syncs clipboard between PC and Phone via WebSocket. """ import os import json import threading import time from config import DATA_DIR CLIPBOARD_FILE = os.path.join(DATA_DIR, "clipboard_sync.json") WS_PORT = 5051 # Separate port for clipboard def _load() -> dict: """Load clipboard history.""" if not os.path.exists(CLIPBOARD_FILE): return {"history": [], "last": ""} try: with open(CLIPBOARD_FILE, "r", encoding="utf-8") as f: return json.load(f) except Exception: return {"history": [], "last": ""} def _save(data: dict) -> None: """Save clipboard history.""" try: os.makedirs(DATA_DIR, exist_ok=True) with open(CLIPBOARD_FILE, "w", encoding="utf-8") as f: json.dump(data, f) except Exception: pass def set_clipboard(text: str, source: str = "pc") -> None: """ Set clipboard and sync. Args: text: Clipboard text source: Source device ('pc' or 'phone') """ if not text: return data = _load() data["last"] = text data["source"] = source data["time"] = time.time() # Add to history (keep last 20) history = data.get("history", []) if text not in history: history.append(text) history = history[-20:] data["history"] = history _save(data) # Broadcast to phone try: _broadcast_to_phone(text, source) except Exception: pass # Set local clipboard if source != "pc": _set_local_clipboard(text) def _set_local_clipboard(text: str) -> None: """Set local Windows clipboard.""" try: import subprocess cmd = f'powershell -command "Set-Clipboard -Value \\"{text}\\"' subprocess.run(cmd, shell=True, capture_output=True) except Exception: pass def get_clipboard() -> str | None: """Get current clipboard.""" data = _load() return data.get("last", "") def get_history() -> list[str]: """Get clipboard history.""" data = _load() return data.get("history", []) # === WebSocket Sync === def _broadcast_to_phone(text: str, source: str) -> None: """Broadcast clipboard change to phone.""" try: import websocket ws = websocket.WebSocket() ws.connect("ws://localhost:5051", timeout=1) ws.send(json.dumps({"type": "clipboard", "text": text, "source": source})) ws.close() except Exception: pass def _ws_server(): """WebSocket server for receiving phone clipboard.""" try: import websocket from websocket import WebSocketServer def on_message(ws, message): try: data = json.loads(message) if data.get("type") == "clipboard": text = data.get("text", "") if text: set_clipboard(text, "phone") _set_local_clipboard(text) except Exception: pass server = WebSocketServer(host="localhost", port=5051) server.on_message = on_message print("[clipboard_sync] WS server started on port 5051") server.serve_forever() except Exception as e: print(f"[clipboard_sync] Server error: {e}") def start_sync_server(): """Start clipboard sync server.""" thread = threading.Thread(target=_ws_server, daemon=True) thread.start() # === Phone API === def phone_set_clipboard(text: str) -> dict: """API for phone to set clipboard.""" set_clipboard(text, "phone") return {"ok": True} def phone_get_clipboard() -> dict: """API for phone to get clipboard.""" return {"ok": True, "clipboard": get_clipboard(), "history": get_history()} # === Voice Commands === def handle_command(command: str, speak) -> bool: """Handle clipboard voice commands.""" c = command.lower() if "copy" in c: # Get text from clipboard text = get_clipboard() if text: speak(f"Copied: {text[:100]}") return True if "clipboard" in c: text = get_clipboard() if text: speak(f"Clipboard: {text[:100]}") else: speak("Clipboard is empty.") return True if "sync clipboard" in c or "clipboard sync" in c: speak("Clipboard synced with phone.") return True return False