Skip to content

Instantly share code, notes, and snippets.

@BHznJNs
Last active September 26, 2025 02:27
Show Gist options
  • Select an option

  • Save BHznJNs/615518f0a28845adf844d29154010a23 to your computer and use it in GitHub Desktop.

Select an option

Save BHznJNs/615518f0a28845adf844d29154010a23 to your computer and use it in GitHub Desktop.
import win32clipboard
import win32con
import win32gui
import win32api
import ctypes
import threading
import time
# 手动定义 win32con 中缺失的常量
WM_CLIPBOARDUPDATE = 0x031D
# 加载 user32.dll 以调用 Add/RemoveClipboardFormatListener
user32 = ctypes.windll.user32
class ClipboardListener(threading.Thread):
"""
在独立的线程中监听 Windows 剪贴板更新。
"""
def __init__(self, callback):
super().__init__()
self.callback = callback
self.daemon = True # 设置为守护线程
self._hwnd = None
# self.ident 是线程启动后由系统分配的线程 ID
def run(self):
"""线程的主体,包含消息循环。"""
# 1. 注册窗口类
class_name = "ClipboardListener"
wc = win32gui.WNDCLASS()
wc.lpfnWndProc = self._wnd_proc
wc.lpszClassName = class_name
wc.hInstance = win32api.GetModuleHandle(None)
try:
class_atom = win32gui.RegisterClass(wc)
except win32gui.error as e:
# 如果类已经注册,会报错,可以忽略
if e.winerror != 1410: # ERROR_CLASS_ALREADY_EXISTS
raise
# 2. 创建一个消息专用窗口
self._hwnd = win32gui.CreateWindow(
class_name, class_name, 0, 0, 0, 0, 0,
win32con.HWND_MESSAGE, 0, wc.hInstance, None
)
if not self._hwnd:
raise RuntimeError("无法创建消息窗口。")
# 3. 注册为剪贴板格式监听者
if not user32.AddClipboardFormatListener(self._hwnd):
raise RuntimeError("无法注册剪贴板监听器。")
print("[Listener Thread] 监听器已启动。")
# 4. 启动消息循环,它会一直运行直到收到 WM_QUIT 消息
win32gui.PumpMessages()
# 消息循环结束后,执行清理
self._cleanup()
def _wnd_proc(self, hwnd, msg, wparam, lparam):
"""窗口消息处理回调函数。"""
if msg == WM_CLIPBOARDUPDATE:
text = self._get_clipboard_text()
if text is not None:
self.callback(text)
# 将其他消息传递给默认处理器
return win32gui.DefWindowProc(hwnd, msg, wparam, lparam)
def _get_clipboard_text(self):
"""从剪贴板安全地获取文本。"""
try:
win32clipboard.OpenClipboard()
if win32clipboard.IsClipboardFormatAvailable(win32con.CF_UNICODETEXT):
return win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT)
except Exception:
return None
finally:
win32clipboard.CloseClipboard()
def stop(self):
"""从主线程调用,向子线程发送退出消息。"""
if not self.ident:
# 如果线程从未启动,则什么都不做
return
print("[Main Thread] 正在发送停止请求...")
# 5. 向子线程的消息队列投递 WM_QUIT 消息
win32api.PostThreadMessage(self.ident, win32con.WM_QUIT, 0, 0)
# 等待线程完全结束
self.join(timeout=5)
print("[Main Thread] 监听线程已停止。")
def _cleanup(self):
"""在子线程内部执行清理。"""
print("[Listener Thread] 正在清理资源...")
# 因为这是在子线程中调用的,所以可以安全地销毁窗口
if self._hwnd:
user32.RemoveClipboardFormatListener(self._hwnd)
win32gui.DestroyWindow(self._hwnd)
# 取消注册窗口类
win32gui.UnregisterClass("ClipboardListener", win32api.GetModuleHandle(None))
print("[Listener Thread] 清理完成。")
# --- 主程序示例 ---
def on_clipboard_update(data):
"""回调函数,当剪贴板更新时由监听线程调用。"""
print("\n--- [Callback Executed] ---")
print(f"主程序收到剪贴板内容:\n{data}")
print("---------------------------\n", end='')
if __name__ == '__main__':
listener = ClipboardListener(on_clipboard_update)
listener.start()
print("[Main Thread] 剪贴板监听器已在后台启动。")
print("[Main Thread] 主线程没有被阻塞,可以继续执行其他任务。")
try:
# 主线程可以做其他事情,这里用一个循环来模拟
while listener.is_alive():
# 可以通过 is_alive() 来判断子线程是否还在运行
time.sleep(1)
except KeyboardInterrupt:
print("\n[Main Thread] 检测到 Ctrl+C,准备退出。")
finally:
# 确保在程序退出前停止监听线程
listener.stop()
print("[Main Thread] 程序已安全退出。")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment