Spaces:
Running
Running
| """ | |
| 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 |