Spaces:
Running
Running
File size: 4,653 Bytes
a31f556 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | """
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 |