Files
kiro-automation-toolkit/sandbox_files/window_analyzer.py
T
hotyi 85efecf0be 初始提交:Kiro IDE 自动化安装完整项目
🚀 项目特性:
- Windows Sandbox 环境支持
- 完整的自动化安装流程
- 智能等待和界面检测机制
- 多种登录方式支持
- 丰富的调试和分析工具

📁 项目结构:
- sandbox/ - 项目根目录
- sandbox_files/ - 沙盒环境脚本
- start_sandbox.ps1 - 主启动脚本
- sandbox_config.wsb - Sandbox 配置

🛠️ 核心功能:
- 自动化 Kiro IDE 安装
- 登录界面自动化
- 窗口和控件分析
- 详细的进度反馈

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 15:53:32 +08:00

315 lines
11 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
窗口控件分析器
功能:连接指定路径的应用程序,等待5秒后分析并打印所有窗口的控件信息
作者:Claude Code Assistant
"""
import time
import sys
import os
from datetime import datetime
from pywinauto import Application
from pywinauto.findwindows import ElementNotFoundError
class WindowAnalyzer:
def __init__(self):
self.app = None
self.analysis_results = []
def connect_to_application(self, app_path, timeout=30):
"""
连接到指定路径的应用程序
Args:
app_path (str): 应用程序路径或进程名
timeout (int): 连接超时时间(秒)
Returns:
bool: 连接是否成功
"""
print(f"🔗 正在连接应用程序: {app_path}")
print(f"⏱️ 连接超时时间: {timeout}秒")
# 特殊处理kiro.exe - 支持安装程序和已安装的应用程序
if "kiro.exe" in app_path.lower():
print("🔍 检测到kiro.exe,尝试连接进程...")
connection_methods = [
# 安装程序连接方法(保留原有功能)
lambda: Application(backend="uia").connect(title_re=".*[Ss]etup.*", timeout=5),
lambda: Application(backend="uia").connect(title_re=".*[Ii]nstall.*", timeout=5),
lambda: Application(backend="uia").connect(title_re=".*安装.*", timeout=5),
lambda: Application(backend="win32").connect(title_re=".*[Ss]etup.*", timeout=5),
lambda: Application(backend="win32").connect(title_re=".*[Ii]nstall.*", timeout=5),
lambda: Application(backend="win32").connect(title_re=".*安装.*", timeout=5),
# 已安装应用程序连接方法(新增功能)
lambda: Application(backend="uia").connect(title_re=".*Kiro.*", timeout=5),
lambda: Application(backend="win32").connect(title_re=".*Kiro.*", timeout=5),
lambda: Application(backend="uia").connect(title_re=".*Getting started.*", timeout=5),
lambda: Application(backend="win32").connect(title_re=".*Getting started.*", timeout=5),
lambda: Application(backend="uia").connect(path=app_path, timeout=5),
lambda: Application(backend="win32").connect(path=app_path, timeout=5),
]
else:
# 原有的连接方式
connection_methods = [
lambda: Application(backend="uia").connect(path=app_path, timeout=5),
lambda: Application(backend="win32").connect(path=app_path, timeout=5),
lambda: Application(backend="uia").connect(title_re=f".*{os.path.basename(app_path)}.*", timeout=5),
]
start_time = time.time()
attempt = 0
while time.time() - start_time < timeout:
attempt += 1
print(f"🔄 连接尝试 {attempt}...")
for i, method in enumerate(connection_methods):
try:
self.app = method()
print(f"✅ 连接成功! 使用方法 {i+1}")
print(f"📋 进程ID: {self.app.process}")
return True
except Exception as e:
print(f" 方法 {i+1} 失败: {str(e)[:50]}...")
continue
time.sleep(1)
print(f"❌ 连接失败: 超时 {timeout}秒")
return False
def analyze_control(self, ctrl, level=0):
"""
分析单个控件的详细信息
Args:
ctrl: pywinauto控件对象
level (int): 层级深度,用于缩进显示
Returns:
dict: 控件信息字典
"""
indent = " " * level
control_info = {}
try:
# 基本信息
control_info['text'] = ctrl.window_text()
control_info['class_name'] = ctrl.class_name()
control_info['visible'] = ctrl.is_visible()
control_info['enabled'] = ctrl.is_enabled()
# UIA特有属性
if hasattr(ctrl, 'element_info'):
try:
control_info['control_type'] = ctrl.element_info.control_type
control_info['automation_id'] = ctrl.element_info.automation_id
control_info['name'] = ctrl.element_info.name
except:
pass
# 位置信息
try:
rect = ctrl.rectangle()
control_info['position'] = f"({rect.left},{rect.top})-({rect.right},{rect.bottom})"
control_info['size'] = f"{rect.width()}x{rect.height()}"
except:
control_info['position'] = "未知"
control_info['size'] = "未知"
except Exception as e:
control_info['error'] = str(e)
return control_info
def print_control_tree(self, window, max_depth=3):
"""
打印窗口的控件树结构
Args:
window: pywinauto窗口对象
max_depth (int): 最大遍历深度
"""
def print_control_recursive(ctrl, level=0):
if level > max_depth:
return
indent = " " * level
info = self.analyze_control(ctrl, level)
# 格式化输出
display_text = info.get('text', '').strip()
if not display_text:
display_text = info.get('name', '').strip()
if not display_text:
display_text = f"[{info.get('class_name', 'Unknown')}]"
status_indicators = []
if not info.get('visible', True):
status_indicators.append("隐藏")
if not info.get('enabled', True):
status_indicators.append("禁用")
status_str = f" ({', '.join(status_indicators)})" if status_indicators else ""
print(f"{indent}├─ {display_text}{status_str}")
# 详细信息
if info.get('control_type'):
print(f"{indent}│ 类型: {info['control_type']}")
if info.get('automation_id'):
print(f"{indent}│ ID: {info['automation_id']}")
if info.get('class_name'):
print(f"{indent}│ 类名: {info['class_name']}")
if info.get('position') != "未知":
print(f"{indent}│ 位置: {info['position']} 大小: {info['size']}")
# 递归处理子控件
try:
children = ctrl.children()
for child in children[:20]: # 限制子控件数量避免输出过多
print_control_recursive(child, level + 1)
if len(children) > 20:
print(f"{indent} └─ ... 还有 {len(children) - 20} 个子控件")
except Exception as e:
if level == 0: # 只在顶级显示错误
print(f"{indent}│ ⚠️ 获取子控件失败: {e}")
try:
print_control_recursive(window)
except Exception as e:
print(f"❌ 分析窗口控件失败: {e}")
def analyze_all_windows(self):
"""
分析应用程序的所有窗口
"""
if not self.app:
print("❌ 应用程序未连接")
return
print("\n" + "="*80)
print("🔍 开始分析窗口控件结构...")
print("="*80)
try:
windows = self.app.windows()
print(f"📊 发现 {len(windows)} 个窗口")
for i, window in enumerate(windows, 1):
try:
window_title = window.window_text()
window_class = window.class_name()
is_visible = window.is_visible()
is_enabled = window.is_enabled()
print(f"\n🪟 窗口 {i}: {window_title or '[无标题]'}")
print(f" 类名: {window_class}")
print(f" 状态: {'可见' if is_visible else '隐藏'}, {'启用' if is_enabled else '禁用'}")
try:
rect = window.rectangle()
print(f" 位置: ({rect.left},{rect.top}) 大小: {rect.width()}x{rect.height()}")
except:
print(" 位置: 无法获取")
print(f" 控件结构:")
self.print_control_tree(window)
except Exception as e:
print(f"❌ 分析窗口 {i} 失败: {e}")
print("-" * 60)
except Exception as e:
print(f"❌ 获取窗口列表失败: {e}")
def save_analysis_to_file(self, filename=None):
"""
将分析结果保存到文件
Args:
filename (str): 输出文件名,默认使用时间戳
"""
if not filename:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"window_analysis_{timestamp}.txt"
try:
# 重定向输出到文件
import io
from contextlib import redirect_stdout
output_buffer = io.StringIO()
with redirect_stdout(output_buffer):
self.analyze_all_windows()
with open(filename, 'w', encoding='utf-8') as f:
f.write(f"窗口控件分析报告\n")
f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("="*80 + "\n\n")
f.write(output_buffer.getvalue())
print(f"📄 分析结果已保存到: {filename}")
except Exception as e:
print(f"❌ 保存文件失败: {e}")
def main():
"""主函数"""
print("🔧 窗口控件分析器")
print("="*50)
# 获取应用程序路径
if len(sys.argv) > 1:
app_path = sys.argv[1]
print(f"📝 使用命令行参数: {app_path}")
else:
app_path = input("请输入应用程序路径或进程名 (例如: kiro.exe 或 C:\\path\\to\\app.exe): ").strip()
if not app_path:
print("❌ 未提供应用程序路径")
return
# 创建分析器
analyzer = WindowAnalyzer()
# 连接应用程序
if not analyzer.connect_to_application(app_path):
print("❌ 无法连接到应用程序,请确保:")
print(" 1. 应用程序正在运行")
print(" 2. 路径或进程名正确")
print(" 3. 应用程序有可见的用户界面")
return
# 等待5秒
print("\n⏳ 等待5秒让应用程序稳定...")
for i in range(5, 0, -1):
print(f" {i}秒...", end='\r')
time.sleep(1)
print(" 完成! ")
# 分析窗口控件
analyzer.analyze_all_windows()
# 询问是否保存到文件
save_choice = input("\n💾 是否保存分析结果到文件? (y/n): ").lower()
if save_choice in ['y', 'yes', '是']:
analyzer.save_analysis_to_file()
print("\n✅ 分析完成!")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n⚠️ 用户中断操作")
except Exception as e:
print(f"\n❌ 程序异常: {e}")
import traceback
traceback.print_exc()