EU-Utility/core/clipboard.py

210 lines
6.2 KiB
Python

"""
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