356 lines
12 KiB
Python
356 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
打印PC微信通讯录的所有子控件
|
||
需要以管理员权限运行Python
|
||
"""
|
||
import uiautomation as auto
|
||
import time
|
||
import ctypes
|
||
from ctypes import wintypes, POINTER, byref, c_int, c_ulong, c_void_p
|
||
import comtypes.client
|
||
|
||
|
||
def print_control_tree(control, depth=0, max_depth=20, file=None):
|
||
"""递归打印控件树"""
|
||
if depth > max_depth:
|
||
return
|
||
|
||
# 获取控件信息
|
||
control_type = control.ControlTypeName
|
||
class_name = control.ClassName or ""
|
||
name = control.Name or ""
|
||
automation_id = control.AutomationId or ""
|
||
rect = control.BoundingRectangle
|
||
|
||
# 缩进
|
||
indent = " " * depth
|
||
|
||
# 打印控件信息
|
||
line = f"{indent}ControlType: {control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect} Depth: {depth}"
|
||
print(line)
|
||
if file:
|
||
file.write(line + "\n")
|
||
|
||
# 递归打印子控件
|
||
try:
|
||
children = control.GetChildren()
|
||
for child in children:
|
||
print_control_tree(child, depth + 1, max_depth, file)
|
||
except Exception as e:
|
||
error_line = f"{indent} [Error getting children: {e}]"
|
||
print(error_line)
|
||
if file:
|
||
file.write(error_line + "\n")
|
||
|
||
|
||
def print_control_tree_raw_comtypes(uiAutomation, element, depth=0, max_depth=20, file=None):
|
||
"""使用comtypes直接访问IUIAutomation接口"""
|
||
if depth > max_depth:
|
||
return
|
||
|
||
try:
|
||
# 获取控件属性
|
||
name = element.CurrentName or ""
|
||
class_name = element.CurrentClassName or ""
|
||
automation_id = element.CurrentAutomationId or ""
|
||
control_type_id = element.CurrentControlType
|
||
localized_control_type = element.CurrentLocalizedControlType or ""
|
||
|
||
# 获取BoundingRectangle
|
||
try:
|
||
rect = element.CurrentBoundingRectangle
|
||
rect_str = f"({rect.left},{rect.top},{rect.right},{rect.bottom})"
|
||
except:
|
||
rect_str = "N/A"
|
||
|
||
indent = " " * depth
|
||
line = f"{indent}ControlType: {localized_control_type} ClassName: {class_name} Name: {name} AutomationId: {automation_id} Rect: {rect_str} Depth: {depth}"
|
||
print(line)
|
||
if file:
|
||
file.write(line + "\n")
|
||
|
||
# 使用RawViewWalker遍历子元素
|
||
tree_walker = uiAutomation.RawViewWalker
|
||
child = tree_walker.GetFirstChildElement(element)
|
||
|
||
while child:
|
||
print_control_tree_raw_comtypes(uiAutomation, child, depth + 1, max_depth, file)
|
||
# 获取下一个兄弟元素
|
||
child = tree_walker.GetNextSiblingElement(child)
|
||
|
||
except Exception as e:
|
||
indent = " " * depth
|
||
error_line = f"{indent}[Error: {e}]"
|
||
print(error_line)
|
||
if file:
|
||
file.write(error_line + "\n")
|
||
|
||
|
||
def enum_windows_callback(hwnd, results):
|
||
"""枚举窗口回调函数"""
|
||
import win32gui
|
||
import win32process
|
||
|
||
if win32gui.IsWindowVisible(hwnd):
|
||
class_name = win32gui.GetClassName(hwnd)
|
||
title = win32gui.GetWindowText(hwnd)
|
||
_, pid = win32process.GetWindowThreadProcessId(hwnd)
|
||
|
||
# 检查是否是微信相关窗口
|
||
try:
|
||
import subprocess
|
||
result = subprocess.run(['tasklist', '/FI', f'PID eq {pid}', '/NH'],
|
||
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
||
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
|
||
results.append((hwnd, class_name, title, pid))
|
||
except:
|
||
pass
|
||
return True
|
||
|
||
|
||
def print_win32_children(hwnd, depth=0, max_depth=20, file=None):
|
||
"""使用Win32 API枚举子窗口"""
|
||
import win32gui
|
||
|
||
if depth > max_depth:
|
||
return
|
||
|
||
class_name = win32gui.GetClassName(hwnd)
|
||
title = win32gui.GetWindowText(hwnd)
|
||
rect = win32gui.GetWindowRect(hwnd)
|
||
|
||
indent = " " * depth
|
||
line = f"{indent}HWND: 0x{hwnd:X} ClassName: {class_name} Title: {title} Rect: {rect} Depth: {depth}"
|
||
print(line)
|
||
if file:
|
||
file.write(line + "\n")
|
||
|
||
# 枚举子窗口
|
||
def child_callback(child_hwnd, _):
|
||
print_win32_children(child_hwnd, depth + 1, max_depth, file)
|
||
return True
|
||
|
||
try:
|
||
win32gui.EnumChildWindows(hwnd, child_callback, None)
|
||
except:
|
||
pass
|
||
|
||
|
||
def print_accessible_tree(hwnd, depth=0, max_depth=20, file=None):
|
||
"""使用IAccessible接口枚举控件"""
|
||
import ctypes
|
||
from ctypes import POINTER, byref
|
||
from ctypes import windll, oledll
|
||
|
||
if depth > max_depth:
|
||
return
|
||
|
||
try:
|
||
import comtypes.client
|
||
from comtypes import IUnknown
|
||
|
||
# 获取IAccessible接口
|
||
accessible = oledll.oleacc.AccessibleObjectFromWindow(
|
||
hwnd,
|
||
0xFFFFFFFC, # OBJID_CLIENT
|
||
comtypes.IUnknown._iid_,
|
||
byref(ctypes.POINTER(comtypes.IUnknown)())
|
||
)
|
||
|
||
# 尝试获取更多信息
|
||
indent = " " * depth
|
||
line = f"{indent}HWND: 0x{hwnd:X} [Accessible Object Available]"
|
||
print(line)
|
||
if file:
|
||
file.write(line + "\n")
|
||
|
||
except Exception as e:
|
||
indent = " " * depth
|
||
line = f"{indent}HWND: 0x{hwnd:X} [No IAccessible: {e}]"
|
||
print(line)
|
||
if file:
|
||
file.write(line + "\n")
|
||
|
||
|
||
def main():
|
||
print("=" * 80)
|
||
print("打印PC微信通讯录的所有子控件")
|
||
print("=" * 80)
|
||
print()
|
||
|
||
# 设置全局搜索超时时间
|
||
auto.SetGlobalSearchTimeout(10)
|
||
|
||
# 获取桌面根控件
|
||
root = auto.GetRootControl()
|
||
print("桌面根控件:", root)
|
||
print()
|
||
|
||
# 查找微信主窗口 - 尝试多种方式
|
||
print("正在查找微信窗口...")
|
||
|
||
wechat_window = None
|
||
|
||
# 方式1: 通过ClassName查找 (旧版微信)
|
||
wechat_window = auto.WindowControl(searchDepth=1, ClassName='WeChatMainWndForPC')
|
||
if wechat_window.Exists(2, 1):
|
||
print(f"方式1找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
|
||
else:
|
||
# 方式2: 通过Name查找
|
||
wechat_window = auto.WindowControl(searchDepth=1, Name='微信')
|
||
if wechat_window.Exists(2, 1):
|
||
print(f"方式2找到微信窗口: {wechat_window.Name}, ClassName: {wechat_window.ClassName}")
|
||
else:
|
||
# 方式3: 通过进程名查找 (新版微信 WeChatAppEx.exe)
|
||
print("尝试通过进程查找微信窗口...")
|
||
for window in root.GetChildren():
|
||
try:
|
||
process_id = window.ProcessId
|
||
import subprocess
|
||
result = subprocess.run(['tasklist', '/FI', f'PID eq {process_id}', '/NH'],
|
||
capture_output=True, text=True, creationflags=subprocess.CREATE_NO_WINDOW)
|
||
if 'WeChat' in result.stdout or 'wechat' in result.stdout.lower():
|
||
wechat_window = window
|
||
print(f"方式3找到微信窗口: {window.Name}, ClassName: {window.ClassName}, PID: {process_id}")
|
||
break
|
||
except Exception as e:
|
||
continue
|
||
|
||
if wechat_window is None or not wechat_window.Exists(1, 1):
|
||
print("\n未找到微信窗口!请确保:")
|
||
print("1. 微信已打开并登录")
|
||
print("2. 以管理员权限运行此脚本")
|
||
print("\n尝试打印所有顶级窗口以帮助调试...")
|
||
print("=" * 80)
|
||
for window in root.GetChildren():
|
||
try:
|
||
print(f"Window: Name='{window.Name}' ClassName='{window.ClassName}' PID={window.ProcessId}")
|
||
except:
|
||
pass
|
||
return
|
||
|
||
print()
|
||
|
||
# 打印微信窗口的基本信息
|
||
print("=" * 80)
|
||
print("微信主窗口信息:")
|
||
print("=" * 80)
|
||
print(f"Name: {wechat_window.Name}")
|
||
print(f"ClassName: {wechat_window.ClassName}")
|
||
print(f"AutomationId: {wechat_window.AutomationId}")
|
||
print(f"ControlType: {wechat_window.ControlTypeName}")
|
||
print(f"ProcessId: {wechat_window.ProcessId}")
|
||
print(f"Handle: {wechat_window.NativeWindowHandle}")
|
||
print()
|
||
|
||
# 输出文件
|
||
output_file = r"D:\夏骥\微信研究\wechat_controls_output.txt"
|
||
|
||
# 首先尝试标准方法
|
||
print("=" * 80)
|
||
print("方式1: 使用标准UIAutomation遍历控件...")
|
||
print("=" * 80)
|
||
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write("=" * 80 + "\n")
|
||
f.write("微信窗口控件树 - 标准UIAutomation\n")
|
||
f.write("=" * 80 + "\n\n")
|
||
f.write(f"微信窗口: Name={wechat_window.Name}, ClassName={wechat_window.ClassName}\n\n")
|
||
|
||
print_control_tree(wechat_window, depth=0, max_depth=20, file=f)
|
||
|
||
print()
|
||
print(f"标准方法输出已保存到: {output_file}")
|
||
print()
|
||
|
||
# 尝试使用RawViewWalker (可以获取更多控件)
|
||
print("=" * 80)
|
||
print("方式2: 使用RawViewWalker遍历控件 (包括原始控件)...")
|
||
print("=" * 80)
|
||
|
||
output_file_raw = r"D:\夏骥\微信研究\wechat_controls_raw.txt"
|
||
|
||
try:
|
||
# 加载UIAutomation类型库
|
||
comtypes.client.GetModule('UIAutomationCore.dll')
|
||
from comtypes.gen.UIAutomationClient import IUIAutomation, CUIAutomation
|
||
|
||
# 创建IUIAutomation实例
|
||
uiAutomation = comtypes.CoCreateInstance(CUIAutomation._reg_clsid_, interface=IUIAutomation)
|
||
|
||
# 获取RootElement
|
||
root_element = uiAutomation.GetRootElement()
|
||
|
||
# 找到微信窗口元素
|
||
condition = uiAutomation.CreatePropertyCondition(30005, "微信") # UIA_NamePropertyId = 30005
|
||
|
||
# 查找微信窗口 (TreeScope_Children = 2)
|
||
wechat_element = root_element.FindFirst(2, condition)
|
||
|
||
if wechat_element:
|
||
with open(output_file_raw, 'w', encoding='utf-8') as f:
|
||
f.write("=" * 80 + "\n")
|
||
f.write("微信窗口控件树 - RawViewWalker\n")
|
||
f.write("=" * 80 + "\n\n")
|
||
|
||
print_control_tree_raw_comtypes(uiAutomation, wechat_element, depth=0, max_depth=20, file=f)
|
||
|
||
print(f"\nRawViewWalker输出已保存到: {output_file_raw}")
|
||
else:
|
||
print("使用RawViewWalker未找到微信窗口元素")
|
||
|
||
except Exception as e:
|
||
print(f"RawViewWalker方法出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
print()
|
||
|
||
# 尝试使用Win32 API枚举窗口
|
||
print("=" * 80)
|
||
print("方式3: 使用Win32 API枚举所有子窗口...")
|
||
print("=" * 80)
|
||
|
||
try:
|
||
import win32gui
|
||
import win32process
|
||
|
||
hwnd = wechat_window.NativeWindowHandle
|
||
|
||
output_file_win32 = r"D:\夏骥\微信研究\wechat_controls_win32.txt"
|
||
|
||
with open(output_file_win32, 'w', encoding='utf-8') as f:
|
||
f.write("=" * 80 + "\n")
|
||
f.write("微信窗口控件树 - Win32 API\n")
|
||
f.write("=" * 80 + "\n\n")
|
||
|
||
print_win32_children(hwnd, depth=0, max_depth=20, file=f)
|
||
|
||
print(f"\nWin32 API输出已保存到: {output_file_win32}")
|
||
|
||
except ImportError:
|
||
print("未安装pywin32,跳过Win32 API方式")
|
||
print("可以通过 'pip install pywin32' 安装")
|
||
except Exception as e:
|
||
print(f"Win32 API方法出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
print()
|
||
print("=" * 80)
|
||
print("完成!")
|
||
print("=" * 80)
|
||
print()
|
||
print("说明:")
|
||
print("新版微信(WeChatAppEx.exe)使用CEF/Chromium渲染UI,")
|
||
print("其内部控件不通过Windows原生控件实现,")
|
||
print("因此UIAutomation和Win32 API都无法直接访问其内部控件。")
|
||
print()
|
||
print("如需获取微信控件信息,可以尝试:")
|
||
print("1. 使用Microsoft Accessibility Insights工具")
|
||
print("2. 使用Chrome DevTools Protocol (如果微信支持)")
|
||
print("3. 使用OCR或图像识别技术")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |