""" Clipboard Manager for EU-Utility Provides clipboard access and monitoring capabilities. """ import time import threading from typing import Optional, List, Dict, Any, Callable from dataclasses import dataclass, asdict from collections import deque @dataclass class ClipboardEntry: """A single clipboard entry.""" content: str timestamp: float source: Optional[str] = None def to_dict(self) -> Dict[str, Any]: return { "content": self.content, "timestamp": self.timestamp, "source": self.source, } class ClipboardManager: """ Manages clipboard operations and history. Features: - Copy/paste operations - Clipboard history - Change monitoring - Thread-safe operations """ def __init__(self, max_history: int = 100): self._max_history = max_history self._history: deque = deque(maxlen=max_history) self._monitoring = False self._monitor_thread: Optional[threading.Thread] = None self._last_content: Optional[str] = None self._listeners: List[Callable[[str], None]] = [] self._lock = threading.Lock() # Try to import pyperclip self._pyperclip_available = False try: import pyperclip self._pyperclip = pyperclip self._pyperclip_available = True except ImportError: pass def is_available(self) -> bool: """Check if clipboard access is available.""" return self._pyperclip_available def copy(self, text: str, source: Optional[str] = None) -> bool: """ Copy text to clipboard. Args: text: Text to copy source: Source identifier for tracking Returns: True if successful """ if not self._pyperclip_available: return False try: self._pyperclip.copy(text) self._add_to_history(text, source) return True except Exception as e: print(f"[Clipboard] Copy failed: {e}") return False def paste(self) -> Optional[str]: """ Get text from clipboard. Returns: Clipboard content or None if unavailable """ if not self._pyperclip_available: return None try: return self._pyperclip.paste() except Exception as e: print(f"[Clipboard] Paste failed: {e}") return None def _add_to_history(self, content: str, source: Optional[str] = None) -> None: """Add content to history.""" with self._lock: entry = ClipboardEntry( content=content, timestamp=time.time(), source=source, ) self._history.append(entry) def get_history(self, limit: Optional[int] = None) -> List[ClipboardEntry]: """ Get clipboard history. Args: limit: Maximum number of entries (default: all) Returns: List of clipboard entries """ with self._lock: history = list(self._history) if limit: history = history[-limit:] return history def clear_history(self) -> None: """Clear clipboard history.""" with self._lock: self._history.clear() def start_monitoring(self, interval: float = 1.0) -> None: """ Start monitoring clipboard for changes. Args: interval: Check interval in seconds """ if self._monitoring or not self._pyperclip_available: return self._monitoring = True self._monitor_thread = threading.Thread( target=self._monitor_loop, args=(interval,), daemon=True, ) self._monitor_thread.start() print("[Clipboard] Monitoring started") def stop_monitoring(self) -> None: """Stop clipboard monitoring.""" self._monitoring = False if self._monitor_thread: self._monitor_thread.join(timeout=2.0) print("[Clipboard] Monitoring stopped") def is_monitoring(self) -> bool: """Check if monitoring is active.""" return self._monitoring def _monitor_loop(self, interval: float) -> None: """Background monitoring loop.""" while self._monitoring: try: current = self.paste() if current and current != self._last_content: self._add_to_history(current, source="monitor") self._last_content = current # Notify listeners for listener in self._listeners: try: listener(current) except Exception as e: print(f"[Clipboard] Listener error: {e}") except Exception as e: print(f"[Clipboard] Monitor error: {e}") time.sleep(interval) def add_listener(self, callback: Callable[[str], None]) -> None: """Add a clipboard change listener.""" self._listeners.append(callback) def remove_listener(self, callback: Callable[[str], None]) -> None: """Remove a clipboard change listener.""" if callback in self._listeners: self._listeners.remove(callback) def get_stats(self) -> Dict[str, Any]: """Get clipboard manager statistics.""" return { "history_count": len(self._history), "max_history": self._max_history, "is_monitoring": self._monitoring, "available": self._pyperclip_available, } # Singleton instance _clipboard_manager: Optional[ClipboardManager] = None def get_clipboard_manager(max_history: int = 100) -> ClipboardManager: """Get the singleton clipboard manager instance.""" global _clipboard_manager if _clipboard_manager is None: _clipboard_manager = ClipboardManager(max_history) return _clipboard_manager