import os import time import struct import shutil import traceback import ctypes from typing import List, Optional, Sequence, Tuple, Union import win32ui import win32gui import win32api import win32con import win32process import win32clipboard import pyperclip import psutil from PIL import Image def GetAllWindows(name=None, classname=None) -> List[Tuple[int, str, str]]: """获取所有顶层窗口的信息,返回 (窗口句柄, 类名, 窗口标题) 列表""" windows = [] def enum_windows_proc(hwnd, extra): class_name = win32gui.GetClassName(hwnd) window_title = win32gui.GetWindowText(hwnd) windows.append((hwnd, class_name, window_title)) win32gui.EnumWindows(enum_windows_proc, None) if name: windows = [i for i in windows if i[-1] == name] if classname: windows = [i for i in windows if i[1] == classname] return windows def GetCursorWindow(): x, y = win32api.GetCursorPos() hwnd = win32gui.WindowFromPoint((x, y)) window_title = win32gui.GetWindowText(hwnd) class_name = win32gui.GetClassName(hwnd) return hwnd, window_title, class_name def set_cursor_pos(x, y): win32api.SetCursorPos((x, y)) def Click(rect): x = (rect.left + rect.right) // 2 y = (rect.top + rect.bottom) // 2 set_cursor_pos(x, y) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0) win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0) def GetPathByHwnd(hwnd) -> Optional[str]: try: thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd) process = psutil.Process(process_id) return process.exe() except Exception: return None def GetVersionByPath(file_path) -> Optional[str]: try: info = win32api.GetFileVersionInfo(file_path, '\\') version = "{}.{}.{}.{}".format(win32api.HIWORD(info['FileVersionMS']), win32api.LOWORD(info['FileVersionMS']), win32api.HIWORD(info['FileVersionLS']), win32api.LOWORD(info['FileVersionLS'])) except Exception: version = None return version def capture(hwnd, bbox) -> Image.Image: """截取指定窗口的指定区域,返回 PIL 图像""" window_rect = win32gui.GetWindowRect(hwnd) win_left, win_top, win_right, win_bottom = window_rect win_width = win_right - win_left win_height = win_bottom - win_top # 获取窗口的设备上下文 hwndDC = win32gui.GetWindowDC(hwnd) mfcDC = win32ui.CreateDCFromHandle(hwndDC) saveDC = mfcDC.CreateCompatibleDC() # 创建位图对象保存整个窗口截图 saveBitMap = win32ui.CreateBitmap() saveBitMap.CreateCompatibleBitmap(mfcDC, win_width, win_height) saveDC.SelectObject(saveBitMap) # 使用PrintWindow捕获整个窗口(包括被遮挡或最小化的窗口) result = ctypes.windll.user32.PrintWindow(hwnd, saveDC.GetSafeHdc(), 3) # 转换为PIL图像 bmpinfo = saveBitMap.GetInfo() bmpstr = saveBitMap.GetBitmapBits(True) im = Image.frombuffer( 'RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1) # 释放资源 win32gui.DeleteObject(saveBitMap.GetHandle()) saveDC.DeleteDC() mfcDC.DeleteDC() win32gui.ReleaseDC(hwnd, hwndDC) # 计算bbox相对于窗口左上角的坐标 bbox_left, bbox_top, bbox_right, bbox_bottom = bbox crop_left = bbox_left - win_left crop_top = bbox_top - win_top crop_right = bbox_right - win_left crop_bottom = bbox_bottom - win_top # 裁剪目标区域 cropped_im = im.crop((crop_left, crop_top, crop_right, crop_bottom)) return cropped_im def GetText(HWND) -> str: length = win32gui.SendMessage(HWND, win32con.WM_GETTEXTLENGTH) * 2 buffer = win32gui.PyMakeBuffer(length) win32api.SendMessage(HWND, win32con.WM_GETTEXT, length, buffer) address, length_ = win32gui.PyGetBufferAddressAndLen(buffer[:-1]) text = win32gui.PyGetString(address, length_)[:int(length / 2)] buffer.release() return text def GetAllWindowExs(HWND) -> Optional[List[list]]: if not HWND: return None handles = [] win32gui.EnumChildWindows( HWND, lambda hwnd, param: param.append([hwnd, win32gui.GetClassName(hwnd), GetText(hwnd)]), handles) return handles def FindWindow(classname=None, name=None, timeout=0) -> int: t0 = time.time() while True: HWND = win32gui.FindWindow(classname, name) if HWND: break if time.time() - t0 > timeout: break time.sleep(0.01) return HWND def FindWinEx(HWND, classname=None, name=None) -> list: hwnds_classname = [] hwnds_name = [] def find_classname(hwnd, classname): classname_ = win32gui.GetClassName(hwnd) if classname_ == classname: if hwnd not in hwnds_classname: hwnds_classname.append(hwnd) def find_name(hwnd, name): name_ = GetText(hwnd) if name in name_: if hwnd not in hwnds_name: hwnds_name.append(hwnd) if classname: win32gui.EnumChildWindows(HWND, find_classname, classname) if name: win32gui.EnumChildWindows(HWND, find_name, name) if classname and name: hwnds = [hwnd for hwnd in hwnds_classname if hwnd in hwnds_name] else: hwnds = hwnds_classname + hwnds_name return hwnds def ClipboardFormats(unit=0, *units) -> List[int]: units = list(units) retry_count = 5 while retry_count > 0: try: win32clipboard.OpenClipboard() try: u = win32clipboard.EnumClipboardFormats(unit) finally: win32clipboard.CloseClipboard() break except Exception: retry_count -= 1 units.append(u) if u: units = ClipboardFormats(u, *units) return units def ReadClipboardData() -> dict: Dict = {} formats = ClipboardFormats() for i in formats: if i == 0: continue retry_count = 5 while retry_count > 0: try: win32clipboard.OpenClipboard() try: data = win32clipboard.GetClipboardData(i) Dict[str(i)] = data finally: win32clipboard.CloseClipboard() break except Exception: retry_count -= 1 return Dict def SetClipboardData(data_dict: dict) -> None: try: # 打开剪贴板 win32clipboard.OpenClipboard() # 清空剪贴板 win32clipboard.EmptyClipboard() # 遍历数据字典,设置各种格式的数据 for format_id, data in data_dict.items(): # 将字符串格式ID转换为整数 format_num = int(format_id) if isinstance(data, str): # 如果是字符串,使用Unicode格式 win32clipboard.SetClipboardData(format_num, data) elif isinstance(data, bytes): # 如果是字节数据,直接设置 win32clipboard.SetClipboardData(format_num, data) except Exception as e: print(f"设置剪贴板数据时出错: {e}") finally: # 关闭剪贴板 try: win32clipboard.CloseClipboard() except Exception: pass def SetClipboardText(text: str) -> None: pyperclip.copy(text) class DROPFILES(ctypes.Structure): _fields_ = [ ("pFiles", ctypes.c_uint), ("x", ctypes.c_long), ("y", ctypes.c_long), ("fNC", ctypes.c_int), ("fWide", ctypes.c_bool), ] def set_files_to_clipboard(file_paths: Union[str, Sequence[str]]) -> bool: """将文件路径列表设置到剪贴板的 CF_HDROP 格式""" # 如果传入的是单个字符串,转换为列表 if isinstance(file_paths, str): file_paths = [file_paths] # 验证文件路径是否存在 valid_paths = [] for path in file_paths: if os.path.exists(path): # 转换为绝对路径 abs_path = os.path.abspath(path) valid_paths.append(abs_path) else: raise ValueError(f"文件路径不存在: {path}") if not valid_paths: return False try: # 打开剪贴板 win32clipboard.OpenClipboard() # 清空剪贴板 win32clipboard.EmptyClipboard() # 计算偏移量(DROPFILES结构大小为20字节) offset = 20 # 构建DROPFILES头部 dropfiles_header = struct.pack(' bool: return set_files_to_clipboard(paths) def PasteFile(folder) -> bool: folder = os.path.realpath(folder) if not os.path.exists(folder): os.makedirs(folder) t0 = time.time() while True: if time.time() - t0 > 10: raise TimeoutError(f"读取剪贴板文件超时!") try: win32clipboard.OpenClipboard() if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_HDROP): files = win32clipboard.GetClipboardData(win32clipboard.CF_HDROP) for file in files: filename = os.path.basename(file) dest_file = os.path.join(folder, filename) shutil.copy2(file, dest_file) return True else: print("剪贴板中没有文件") return False except Exception: pass finally: try: win32clipboard.CloseClipboard() except Exception: pass def enum_windows_by_pid(pid: int) -> List[int]: window_list = [] def enum_callback(hwnd, lParam): # 获取窗口的进程ID _, process_id = win32process.GetWindowThreadProcessId(hwnd) # 如果是目标进程,检查窗口是否可见 if process_id == pid: if is_window_visible(hwnd): window_list.append(hwnd) return True win32gui.EnumWindows(enum_callback, None) return window_list def is_window_visible(hwnd) -> bool: # 检查窗口是否可见 style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE) # 检查窗口是否有 WS_VISIBLE 标志,且不是最小化的 if style & win32con.WS_VISIBLE and not win32gui.IsIconic(hwnd): return True return False def get_windows_by_pid(pid: int) -> List[int]: while True: try: windows = enum_windows_by_pid(pid) return windows except Exception: time.sleep(0.1)