302 lines
9.3 KiB
Python
302 lines
9.3 KiB
Python
|
|
import os
|
|||
|
|
import time
|
|||
|
|
import shutil
|
|||
|
|
import win32ui
|
|||
|
|
import win32gui
|
|||
|
|
import win32api
|
|||
|
|
import win32con
|
|||
|
|
import win32process
|
|||
|
|
import win32clipboard
|
|||
|
|
import pyperclip
|
|||
|
|
import ctypes
|
|||
|
|
from PIL import Image
|
|||
|
|
from wxauto import uiautomation as uia
|
|||
|
|
|
|||
|
|
def GetAllWindows():
|
|||
|
|
"""
|
|||
|
|
获取所有窗口的信息,返回一个列表,每个元素包含 (窗口句柄, 类名, 窗口标题)
|
|||
|
|
"""
|
|||
|
|
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)
|
|||
|
|
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):
|
|||
|
|
"""通过窗口句柄获取进程可执行文件路径 - 使用pywin32"""
|
|||
|
|
try:
|
|||
|
|
thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd)
|
|||
|
|
|
|||
|
|
# 获取进程句柄
|
|||
|
|
PROCESS_QUERY_INFORMATION = 0x0400
|
|||
|
|
PROCESS_VM_READ = 0x0010
|
|||
|
|
process_handle = win32api.OpenProcess(
|
|||
|
|
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
|||
|
|
False,
|
|||
|
|
process_id
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# 获取可执行文件路径
|
|||
|
|
exe_path = win32process.GetModuleFileNameEx(process_handle, 0)
|
|||
|
|
|
|||
|
|
# 关闭句柄
|
|||
|
|
win32api.CloseHandle(process_handle)
|
|||
|
|
|
|||
|
|
return exe_path
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Error: {e}")
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def GetVersionByPath(file_path):
|
|||
|
|
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:
|
|||
|
|
version = None
|
|||
|
|
return version
|
|||
|
|
|
|||
|
|
def GetWindowRect(hwnd):
|
|||
|
|
return win32gui.GetWindowRect(hwnd)
|
|||
|
|
|
|||
|
|
def capture(hwnd, bbox):
|
|||
|
|
# 获取窗口的屏幕坐标
|
|||
|
|
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):
|
|||
|
|
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):
|
|||
|
|
if not HWND:
|
|||
|
|
return
|
|||
|
|
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 FindTopLevelControl(classname=None, name=None, timeout=3):
|
|||
|
|
hwnd = FindWindow(classname, name, timeout)
|
|||
|
|
if hwnd:
|
|||
|
|
return uia.ControlFromHandle(hwnd)
|
|||
|
|
else:
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
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):
|
|||
|
|
units = list(units)
|
|||
|
|
retry_count = 5
|
|||
|
|
while retry_count > 0:
|
|||
|
|
try:
|
|||
|
|
win32clipboard.OpenClipboard()
|
|||
|
|
try:
|
|||
|
|
u = win32clipboard.EnumClipboardFormats(unit)
|
|||
|
|
finally:
|
|||
|
|
win32clipboard.CloseClipboard()
|
|||
|
|
break
|
|||
|
|
except Exception as e:
|
|||
|
|
retry_count -= 1
|
|||
|
|
units.append(u)
|
|||
|
|
if u:
|
|||
|
|
units = ClipboardFormats(u, *units)
|
|||
|
|
return units
|
|||
|
|
|
|||
|
|
def ReadClipboardData():
|
|||
|
|
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 as e:
|
|||
|
|
retry_count -= 1
|
|||
|
|
return Dict
|
|||
|
|
|
|||
|
|
def SetClipboardText(text: str):
|
|||
|
|
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),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
pDropFiles = DROPFILES()
|
|||
|
|
pDropFiles.pFiles = ctypes.sizeof(DROPFILES)
|
|||
|
|
pDropFiles.fWide = True
|
|||
|
|
matedata = bytes(pDropFiles)
|
|||
|
|
|
|||
|
|
def SetClipboardFiles(paths):
|
|||
|
|
for file in paths:
|
|||
|
|
if not os.path.exists(file):
|
|||
|
|
raise FileNotFoundError(f"file ({file}) not exists!")
|
|||
|
|
files = ("\0".join(paths)).replace("/", "\\")
|
|||
|
|
data = files.encode("U16")[2:]+b"\0\0"
|
|||
|
|
t0 = time.time()
|
|||
|
|
while True:
|
|||
|
|
if time.time() - t0 > 10:
|
|||
|
|
raise TimeoutError(f"设置剪贴板文件超时! --> {paths}")
|
|||
|
|
try:
|
|||
|
|
win32clipboard.OpenClipboard()
|
|||
|
|
win32clipboard.EmptyClipboard()
|
|||
|
|
win32clipboard.SetClipboardData(win32clipboard.CF_HDROP, matedata+data)
|
|||
|
|
break
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
finally:
|
|||
|
|
try:
|
|||
|
|
win32clipboard.CloseClipboard()
|
|||
|
|
except:
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
def PasteFile(folder):
|
|||
|
|
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:
|
|||
|
|
pass
|
|||
|
|
finally:
|
|||
|
|
win32clipboard.CloseClipboard()
|
|||
|
|
|
|||
|
|
def IsRedPixel(uicontrol):
|
|||
|
|
rect = uicontrol.BoundingRectangle
|
|||
|
|
hwnd = uicontrol.GetAncestorControl(lambda x,y:x.ClassName=='WeChatMainWndForPC').NativeWindowHandle
|
|||
|
|
bbox = (rect.left, rect.top, rect.right, rect.bottom)
|
|||
|
|
img = capture(hwnd, bbox)
|
|||
|
|
return any(p[0] > p[1] and p[0] > p[2] for p in img.getdata())
|