Last active
September 26, 2025 02:27
-
-
Save BHznJNs/615518f0a28845adf844d29154010a23 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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