-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclipboard_monitor.py
More file actions
85 lines (71 loc) · 2.37 KB
/
Copy pathclipboard_monitor.py
File metadata and controls
85 lines (71 loc) · 2.37 KB
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
"""
Clipboard monitor — polls for new URLs and fires a callback.
Uses win32clipboard for reliable Windows clipboard access.
"""
import re
import threading
import time
import logging
import ctypes
import ctypes.wintypes
log = logging.getLogger(__name__)
URL_RE = re.compile(
r"https?://[^\s\"'<>\]\[(){}\|\\^`]+"
r"|(?:www\.[a-zA-Z0-9\-]+\.[a-zA-Z]{2,}[^\s\"'<>]*)",
re.IGNORECASE,
)
CF_UNICODETEXT = 13
def _get_clipboard_text() -> str | None:
"""Read clipboard text using win32 API directly (no extra import needed)."""
try:
import win32clipboard
win32clipboard.OpenClipboard(None)
try:
if win32clipboard.IsClipboardFormatAvailable(CF_UNICODETEXT):
return win32clipboard.GetClipboardData(CF_UNICODETEXT)
finally:
win32clipboard.CloseClipboard()
except Exception:
pass
return None
class ClipboardMonitor(threading.Thread):
"""
Background thread that polls the clipboard every 600ms.
Calls `on_url(url)` when a new URL is detected.
"""
def __init__(self, on_url, poll_interval: float = 0.6):
super().__init__(daemon=True, name="ClipboardMonitor")
self._on_url = on_url
self._interval = poll_interval
self._stop_event = threading.Event()
self._last_text: str = ""
self._last_reported_url: str = ""
def run(self):
log.info("Clipboard monitor started")
while not self._stop_event.wait(self._interval):
try:
self._tick()
except Exception as e:
log.debug("Clipboard tick error: %s", e)
log.info("Clipboard monitor stopped")
def stop(self):
self._stop_event.set()
def _tick(self):
text = _get_clipboard_text()
if not text or text == self._last_text:
return
self._last_text = text
# Find all URLs in the clipboard content
urls = URL_RE.findall(text.strip())
if not urls:
return
# Take the first (or only) URL; skip if same as last reported
url = urls[0].rstrip(".,;:!?)")
if url == self._last_reported_url:
return
self._last_reported_url = url
log.debug("Clipboard URL detected: %s", url)
try:
self._on_url(url)
except Exception as e:
log.error("on_url callback error: %s", e)