210 lines
6.2 KiB
Python
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
|