jarvis-cloud / modules /clipboard_sync.py
Jarvis2345's picture
Squash history — remove all prior commits (secret hygiene, S4)
a31f556
Raw
History Blame
4.65 kB
"""
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