完善UI自动动画的选择元素、滚动元素、展开元素功能

This commit is contained in:
fofolee
2025-01-18 01:14:40 +08:00
parent 76f710323e
commit 053e9793c3
4 changed files with 430 additions and 171 deletions

View File

@@ -63,6 +63,9 @@ public class AutomationManager
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorPos(out Point lpPoint); private static extern bool GetCursorPos(out Point lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point point);
[DllImport("user32.dll")] [DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index); static extern int GetWindowLong(IntPtr hwnd, int index);
@@ -335,8 +338,15 @@ public class AutomationManager
{ {
// 通过窗口句柄查找 // 通过窗口句柄查找
int handle = int.Parse(windowArg); int handle = int.Parse(windowArg);
try
{
root = AutomationElement.FromHandle(new IntPtr(handle)); root = AutomationElement.FromHandle(new IntPtr(handle));
} }
catch
{
throw new Exception("无法获取指定的窗口");
}
}
else else
{ {
// 使用当前活动窗口 // 使用当前活动窗口
@@ -439,80 +449,142 @@ public class AutomationManager
private static void ShowHelp() private static void ShowHelp()
{ {
string help = @"UI自动化工具 Console.WriteLine(@"UI自动化工具 v1.0
用法: automation.exe -type <操作类型> [参数] 用法: automation.exe <操作类型> [参数]
通用参数:
-window <窗口句柄> 指定要操作的窗口,如果不指定则使用当前活动窗口
操作类型: 操作类型:
1. inspect - 检查元素 1. inspect - 检查元素
无需其他参数,点击要检查的元素即可 无需其他参数,点击要检查的元素即可
2. click - 点击元素 2. click - 点击指定元素
-xpath <XPath路径> 参数: -xpath <XPath路径> [-window <窗口句柄>]
-id <AutomationId> 或 适用控件: 所有可点击的控件,包括:
-name <名称> 或 - Button (按钮)
-condition ""name=xx;type=Button"" - MenuItem (菜单项)
- TreeItem (树项)
- ListItem (列表项)
- TabItem (标签页)
- RadioButton (单选按钮)
- CheckBox (复选框)
示例:
- 点击按钮: -xpath ""//Button[@Name='确定']""
- 点击菜单项: -xpath ""//MenuBar/MenuItem[@Name='文件']/MenuItem[@Name='打开']""
- 点击树节点: -xpath ""//Tree/TreeItem[@Name='节点1']""
3. setvalue - 设置值 3. setvalue - 设置值
-xpath <XPath路径> -value <新值> 参数: -xpath <XPath路径> -value <新值> [-window <窗口句柄>]
4. getvalue - 获取值 4. getvalue - 获取值
-xpath <XPath路径> 参数: -xpath <XPath路径> [-window <窗口句柄>]
5. select - 选择项目 5. select - 选择指定项目
-xpath <XPath路径> -item <项目名称> 参数: -xpath <XPath路径> -item <项目名称> [-window <窗口句柄>]
适用控件及其特性:
- ComboBox (组合框)
* 自动展开下拉列表
* 支持选择 ListItem
- TreeView (树视图)
* 支持选择所有层级的 TreeItem
* 使用完整路径查找
- ListBox (列表框)
* 支持选择直接子项 ListItem
- DataGrid (数据网格)
* 支持选择 DataItem 和 ListItem
- Table (表格)
* 支持选择 DataItem 和 ListItem
- Tab (标签页)
* 支持选择直接子项 TabItem
- MenuBar/Menu (菜单)
* 支持选择所有层级的 MenuItem
- RadioButton (单选按钮)
* 直接选择匹配名称的按钮
- CheckBox (复选框)
* 直接选择匹配名称的复选框
示例:
- 选择下拉框选项: -xpath ""//ComboBox"" -item ""选项1""
- 选择树节点: -xpath ""//Tree"" -item ""父节点/子节点""
- 选择列表项: -xpath ""//List"" -item ""列表项1""
- 选择标签页: -xpath ""//Tab"" -item ""标签页2""
- 选择菜单项: -xpath ""//MenuBar"" -item ""文件""
- 选择单选按钮: -xpath ""//RadioButton[@Name='选项1']"" -item ""选项1""
6. expand - 展开/折叠 6. expand - 展开/折叠
-xpath <XPath路径> -expand <true/false> 参数: -xpath <XPath路径> -expand <true/false> [-window <窗口句柄>]
适用控件及其特性:
- TreeItem (树节点) 展开/折叠子节点
- ComboBox (组合框) 展开/折叠下拉列表
- Menu (菜单) 支持展开/折叠子菜单
- GroupBox (分组框) 展开/折叠内容区域
- Expander (展开器) 展开/折叠详细内容
示例:
- 展开树节点: -xpath ""//Tree/TreeItem[@Name='父节点']"" -expand true
- 折叠树节点: -xpath ""//Tree/TreeItem[@Name='父节点']"" -expand false
- 展开下拉框: -xpath ""//ComboBox"" -expand true
- 折叠下拉框: -xpath ""//ComboBox"" -expand false
- 展开菜单: -xpath ""//Menu/MenuItem[@Name='文件']"" -expand true
- 展开分组: -xpath ""//GroupBox[@Name='详细信息']"" -expand true
- 展开内容: -xpath ""//Expander"" -expand true
7. scroll - 滚动 7. scroll - 滚动
-xpath <XPath路径> -direction <vertical/horizontal> -amount <0-100> 参数: -xpath <XPath路径> -direction <vertical/horizontal> -amount <0-100> [-window <窗口句柄>]
适用控件及其特性:
- ScrollBar (滚动条)
- ListBox (列表框)
- ComboBox (组合框)
- DataGrid (数据网格)
- TreeView (树视图)
- TextBox (文本框)
- Document (文档)
示例:
- 垂直滚动列表到底部: -xpath ""//List"" -direction vertical -amount 100
- 水平滚动表格到中间: -xpath ""//DataGrid"" -direction horizontal -amount 50
- 垂直滚动文本框到顶部: -xpath ""//Edit"" -direction vertical -amount 0
- 水平滚动文档到最右: -xpath ""//Document"" -direction horizontal -amount 100
8. wait - 等待元素 8. wait - 等待元素
-xpath <XPath路径> -timeout <秒数> 参数: -xpath <XPath路径> -timeout <秒数> [-window <窗口句柄>]
9. focus - 设置焦点 9. focus - 设置焦点
-xpath <XPath路径> 参数: -xpath <XPath路径> [-window <窗口句柄>]
10. highlight - 高亮显示 10. highlight - 高亮显示
-xpath <XPath路径> -duration <秒数> 参数: -xpath <XPath路径> -duration <秒数> [-window <窗口句柄>]
11. sendkeys - 发送按键 11. sendkeys - 发送按键
-xpath <XPath路径> -keys <按键> 参数: -xpath <XPath路径> -keys <按键> [-window <窗口句柄>]
按键格式说明: 按键格式说明:
- 普通字符直接输入,如 ""abc"" - 普通字符直接输入,如 ""abc""
- 特殊按键用 {} 包围,如 {ENTER}{TAB} - 特殊按键用 {} 包围,如 ""{ENTER}""、""{TAB}""
- 组合键,如 ^c 表示 Ctrl+C - 组合键,如 ""^c"" 表示 Ctrl+C
- 支持的特殊按键: 支持的特殊按键:
{BACKSPACE}, {BS}, {BKSP} - 退格键 - {BACKSPACE}, {BS}, {BKSP} - 退格键
{BREAK} - Break键 - {BREAK} - Break键
{CAPSLOCK} - Caps Lock键 - {CAPSLOCK} - Caps Lock键
{DELETE}, {DEL} - Delete键 - {DELETE}, {DEL} - Delete键
{DOWN} - 向下键 - {DOWN} - 向下键
{END} - End键 - {END} - End键
{ENTER}, {RETURN} - Enter键 - {ENTER}, {RETURN} - Enter键
{ESC} - Esc键 - {ESC} - Esc键
{HELP} - Help键 - {HELP} - Help键
{HOME} - Home键 - {HOME} - Home键
{INSERT}, {INS} - Insert键 - {INSERT}, {INS} - Insert键
{LEFT} - 向左键 - {LEFT} - 向左键
{NUMLOCK} - Num Lock键 - {NUMLOCK} - Num Lock键
{PGDN} - Page Down键 - {PGDN} - Page Down键
{PGUP} - Page Up键 - {PGUP} - Page Up键
{PRTSC} - Print Screen键 - {PRTSC} - Print Screen键
{RIGHT} - 向右键 - {RIGHT} - 向右键
{SCROLLLOCK} - Scroll Lock键 - {SCROLLLOCK} - Scroll Lock键
{TAB} - Tab键 - {TAB} - Tab键
{UP} - 向上键 - {UP} - 向上键
{F1} - {F12} - 功能键 - {F1} - {F12} - 功能键
{ADD} - 数字键盘加号键 - {ADD} - 数字键盘加号键
{SUBTRACT} - 数字键盘减号键 - {SUBTRACT} - 数字键盘减号键
{MULTIPLY} - 数字键盘乘号键 - {MULTIPLY} - 数字键盘乘号键
{DIVIDE} - 数字键盘除号键 - {DIVIDE} - 数字键盘除号键
{NUMPAD0} - {NUMPAD9} - 数字键盘数字键 - {NUMPAD0} - {NUMPAD9} - 数字键盘数字键
修饰键: 修饰键:
+ (加号) - SHIFT + (加号) - SHIFT
@@ -520,14 +592,32 @@ public class AutomationManager
% (百分号) - ALT % (百分号) - ALT
示例: 示例:
""Hello"" - 输入 Hello - 输入文本: -keys ""Hello""
""{ENTER}"" - 按 Enter 键 - 按Enter键: -keys ""{ENTER}""
""^c"" - 按 Ctrl+C - 按Ctrl+C: -keys ""^c""
""^{HOME}"" - 按 Ctrl+Home - 按Ctrl+Home: -keys ""^{HOME}""
""%{F4}"" - 按 Alt+F4 - 按Alt+F4: -keys ""%{F4}""
""+{TAB}"" - 按 Shift+Tab"; - 按Shift+Tab: -keys ""+{TAB}""
Console.WriteLine(help); 通用参数:
-window <窗口句柄> 指定要操作的窗口,如果不指定则使用当前活动窗口
元素定位方式:
1. XPath定位推荐
-xpath <XPath路径>
示例: -xpath ""//Button[@Name='确定']""
2. AutomationId定位
-id <AutomationId>
示例: -id ""btnOK""
3. Name定位
-name <名称>
示例: -name ""确定""
4. 组合条件定位
-condition ""name=xx;type=Button;class=xx;automation=xx""
示例: -condition ""name=确定;type=Button""");
} }
private static string GetArgumentValue(string[] args, string key, bool checkNextArg = true) private static string GetArgumentValue(string[] args, string key, bool checkNextArg = true)
@@ -600,16 +690,94 @@ public class AutomationManager
throw new Exception("必须指定要选择的项目"); throw new Exception("必须指定要选择的项目");
} }
try // 根据控件类型使用不同的查找策略
TreeScope searchScope;
Condition searchCondition;
int controlTypeId = element.Current.ControlType.Id;
if (controlTypeId == ControlType.ComboBox.Id)
{ {
// ComboBox需要先展开
var expandPattern = element.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern;
if (expandPattern != null)
{
expandPattern.Expand();
System.Threading.Thread.Sleep(100);
}
searchScope = TreeScope.Descendants;
searchCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem);
}
else if (controlTypeId == ControlType.Tree.Id)
{
// TreeView查找所有TreeItem
searchScope = TreeScope.Descendants;
searchCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TreeItem);
}
else if (controlTypeId == ControlType.List.Id || controlTypeId == ControlType.DataGrid.Id || controlTypeId == ControlType.Table.Id)
{
// ListBox, DataGrid, Table 查找直接子项
searchScope = TreeScope.Children;
searchCondition = new OrCondition(
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ListItem),
new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.DataItem)
);
}
else if (controlTypeId == ControlType.Tab.Id)
{
// Tab查找直接子项
searchScope = TreeScope.Children;
searchCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TabItem);
}
else if (controlTypeId == ControlType.MenuBar.Id || controlTypeId == ControlType.Menu.Id)
{
// 菜单项查找
searchScope = TreeScope.Descendants;
searchCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuItem);
}
else if (controlTypeId == ControlType.RadioButton.Id || controlTypeId == ControlType.CheckBox.Id)
{
// 单选框和复选框直接选择自身
if (element.Current.Name == item)
{
var selectionItemPattern = element.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
if (selectionItemPattern != null)
{
selectionItemPattern.Select();
Console.WriteLine("true");
return;
}
}
throw new Exception(string.Format("找不到指定的选项: {0}", item));
}
else
{
// 对于其他类型的控件尝试直接使用SelectionPattern
var selectionPattern = element.GetCurrentPattern(SelectionPattern.Pattern) as SelectionPattern; var selectionPattern = element.GetCurrentPattern(SelectionPattern.Pattern) as SelectionPattern;
if (selectionPattern != null) if (selectionPattern != null)
{ {
var children = element.FindAll(TreeScope.Children, Condition.TrueCondition); searchScope = TreeScope.Children;
searchCondition = Condition.TrueCondition;
}
else
{
throw new Exception("不支持的控件类型");
}
}
// 查找所有子项
var children = element.FindAll(searchScope, searchCondition);
if (children.Count == 0)
{
throw new Exception("未找到可选择的项目");
}
// 遍历查找匹配项并选择
foreach (AutomationElement child in children) foreach (AutomationElement child in children)
{ {
if (child.Current.Name == item) if (child.Current.Name == item)
{ {
// 尝试使用SelectionItemPattern选择
var selectionItemPattern = child.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern; var selectionItemPattern = child.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
if (selectionItemPattern != null) if (selectionItemPattern != null)
{ {
@@ -619,15 +787,8 @@ public class AutomationManager
} }
} }
} }
throw new Exception("找不到指定的项目: " + item);
}
throw new Exception("元素不支持选择操作"); throw new Exception(string.Format("找不到指定的项目: {0}", item));
}
catch (Exception ex)
{
throw new Exception("选择操作失败: " + ex.Message);
}
} }
private static void ExpandElement(AutomationElement element, bool expand) private static void ExpandElement(AutomationElement element, bool expand)
@@ -655,24 +816,82 @@ public class AutomationManager
private static void ScrollElement(AutomationElement element, string direction, double amount) private static void ScrollElement(AutomationElement element, string direction, double amount)
{ {
if (element == null)
{
throw new Exception("未找到目标元素");
}
try try
{ {
var scrollPattern = element.GetCurrentPattern(ScrollPattern.Pattern) as ScrollPattern; // 首先尝试使用 ScrollPattern
if (scrollPattern != null) object scrollPattern;
if (element.TryGetCurrentPattern(ScrollPattern.Pattern, out scrollPattern))
{ {
var scroll = scrollPattern as ScrollPattern;
if (direction.ToLower() == "horizontal") if (direction.ToLower() == "horizontal")
scrollPattern.SetScrollPercent(amount, ScrollPattern.NoScroll); {
if (!scroll.Current.HorizontallyScrollable)
{
throw new Exception("元素不支持水平滚动");
}
scroll.SetScrollPercent(amount, ScrollPattern.NoScroll);
}
else else
scrollPattern.SetScrollPercent(ScrollPattern.NoScroll, amount); {
if (!scroll.Current.VerticallyScrollable)
{
throw new Exception("元素不支持垂直滚动");
}
scroll.SetScrollPercent(ScrollPattern.NoScroll, amount);
}
Console.WriteLine("true"); Console.WriteLine("true");
return; return;
} }
// 尝试使用 ScrollItemPattern
object scrollItemPattern;
if (element.TryGetCurrentPattern(ScrollItemPattern.Pattern, out scrollItemPattern))
{
var scrollItem = scrollItemPattern as ScrollItemPattern;
scrollItem.ScrollIntoView();
Console.WriteLine("true");
return;
}
// 检查是否有滚动条子元素
var scrollBars = element.FindAll(TreeScope.Children, new PropertyCondition(
AutomationElement.ControlTypeProperty, ControlType.ScrollBar));
if (scrollBars.Count > 0)
{
foreach (AutomationElement scrollBar in scrollBars)
{
// 获取滚动条的方向
bool isHorizontal = scrollBar.Current.BoundingRectangle.Width > scrollBar.Current.BoundingRectangle.Height;
if ((direction.ToLower() == "horizontal" && isHorizontal) ||
(direction.ToLower() == "vertical" && !isHorizontal))
{
// 使用 RangeValuePattern 设置滚动条的值
var rangeValuePattern = scrollBar.GetCurrentPattern(RangeValuePattern.Pattern) as RangeValuePattern;
if (rangeValuePattern != null)
{
double maxValue = rangeValuePattern.Current.Maximum;
double minValue = rangeValuePattern.Current.Minimum;
double targetValue = minValue + ((maxValue - minValue) * amount / 100);
rangeValuePattern.SetValue(targetValue);
Console.WriteLine("true");
return;
}
}
}
}
throw new Exception("元素不支持滚动操作"); throw new Exception("元素不支持滚动操作");
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exception("滚动操作失败: " + ex.Message); throw new Exception(string.Format("滚动操作失败: {0}", ex.Message));
} }
} }
@@ -842,7 +1061,11 @@ public class AutomationManager
// 循环直到找到根元素 // 循环直到找到根元素
while (current != null && current != AutomationElement.RootElement) while (current != null && current != AutomationElement.RootElement)
{ {
var parent = walker.GetParent(current); // 获取父元素,如果获取失败,则停止遍历
AutomationElement parent;
try
{
parent = walker.GetParent(current);
// 是否是最后一个元素 // 是否是最后一个元素
bool isLastElement = (parent == AutomationElement.RootElement || parent == null); bool isLastElement = (parent == AutomationElement.RootElement || parent == null);
// 是否是句柄不为0的窗口 // 是否是句柄不为0的窗口
@@ -882,8 +1105,19 @@ public class AutomationManager
path.Insert(0, pathSegment); path.Insert(0, pathSegment);
} }
}
catch
{
break;
}
current = parent; current = parent;
}
if (windowHandle == IntPtr.Zero)
{
Point currentMousePosition;
GetCursorPos(out currentMousePosition);
windowHandle = WindowFromPoint(currentMousePosition);
} }
return new ElementHierarchyInfo return new ElementHierarchyInfo
@@ -1002,32 +1236,6 @@ public class AutomationManager
} }
} }
// 将 HandleKeyPress 方法移到类级别
private static void HandleKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)27) // ESC键
{
mouseTimer.Stop();
overlayForm.Close();
previewForm.Close();
Environment.Exit(0);
}
else if (e.KeyChar == 'c' || e.KeyChar == 'C') // 添加复制功能
{
if (lastElement != null)
{
try
{
Clipboard.SetText(lastElement.Current.Name);
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error copying name: {0}", ex.Message));
}
}
}
}
// 元素检查器 // 元素检查器
private static void InspectElement(string[] args) private static void InspectElement(string[] args)
{ {
@@ -1243,7 +1451,8 @@ public class AutomationManager
"名称: {2}\r\n" + "名称: {2}\r\n" +
"大小: {3}x{4}\r\n" + "大小: {3}x{4}\r\n" +
"类型: {5}\r\n" + "类型: {5}\r\n" +
"C复制名称ESC退出", "C复制名称X复制名称并退出\r\n" +
"ESC退出",
cursorPos.X, cursorPos.X,
cursorPos.Y, cursorPos.Y,
elementName, elementName,
@@ -1452,4 +1661,39 @@ public class AutomationManager
return element; return element;
} }
private static void stopInspect()
{
mouseTimer.Stop();
overlayForm.Close();
previewForm.Close();
Environment.Exit(0);
}
private static void HandleKeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == (char)27) // ESC键
{
stopInspect();
}
else if (e.KeyChar == 'c' || e.KeyChar == 'C' || e.KeyChar == 'x' || e.KeyChar == 'X') // 添加复制功能
{
if (lastElement != null)
{
try
{
Clipboard.SetText(lastElement.Current.Name);
if (e.KeyChar == 'x' || e.KeyChar == 'X')
{
stopInspect();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(string.Format("Error copying name: {0}", ex.Message));
}
}
}
}
} }

View File

@@ -164,31 +164,43 @@ const runCsharpFeature = async (feature, args = [], options = {}) => {
const { alwaysBuildNewExe = window.utools.isDev(), killPrevious = true } = const { alwaysBuildNewExe = window.utools.isDev(), killPrevious = true } =
options; options;
try { try {
if (killPrevious && currentChild) {
currentChild.kill();
}
const featureExePath = await getCsharpFeatureExe( const featureExePath = await getCsharpFeatureExe(
feature, feature,
alwaysBuildNewExe alwaysBuildNewExe
); );
if (killPrevious && currentChild) {
quickcommand.kill(currentChild.pid, "SIGKILL");
}
console.log(featureExePath, args.join(" ")); console.log(featureExePath, args.join(" "));
currentChild = child_process.execFile( currentChild = child_process.spawn(featureExePath, args, {
featureExePath,
args,
{
encoding: null, encoding: null,
}, windowsHide: true,
(err, stdout, stderr) => {
console.log({
err,
stdout: iconv.decode(stdout, "gbk"),
stderr: iconv.decode(stderr, "gbk"),
}); });
if (err || Buffer.byteLength(stderr) > 0)
reject(iconv.decode(stderr || stdout, "gbk")); let stdoutData = Buffer.from([]);
else reslove(iconv.decode(stdout, "gbk")); let stderrData = Buffer.from([]);
}
currentChild.stdout.on("data", (data) => {
stdoutData = Buffer.concat([stdoutData, data]);
});
currentChild.stderr.on("data", (data) => {
stderrData = Buffer.concat([stderrData, data]);
});
currentChild.on("error", (err) => {
reject(err.toString());
});
currentChild.on("close", (code) => {
if (code !== 0 || stderrData.length > 0) {
reject(
iconv.decode(stderrData.length ? stderrData : stdoutData, "gbk")
); );
} else {
reslove(iconv.decode(stdoutData, "gbk"));
}
});
} catch (error) { } catch (error) {
return reject(error.toString()); return reject(error.toString());
} }

View File

@@ -46,7 +46,7 @@ async function runAutomation(
// 特定命令的参数处理 // 特定命令的参数处理
switch (type) { switch (type) {
case "inspect": case "inspect":
if (params) { if (params.usePosition) {
args.push("-position"); args.push("-position");
if (params.x && params.y) { if (params.x && params.y) {
args.push(`${params.x},${params.y}`); args.push(`${params.x},${params.y}`);
@@ -130,7 +130,10 @@ async function runAutomation(
module.exports = { module.exports = {
inspect: () => runAutomation("inspect"), inspect: () => runAutomation("inspect"),
inspectPosition: (position) => inspectPosition: (position) =>
runAutomation("inspect", null, null, null, position || {}), runAutomation("inspect", null, null, null, {
...position,
usePosition: true,
}),
click: (...args) => runAutomation("click", ...args), click: (...args) => runAutomation("click", ...args),
setvalue: (...args) => runAutomation("setvalue", ...args), setvalue: (...args) => runAutomation("setvalue", ...args),
getvalue: (...args) => runAutomation("getvalue", ...args), getvalue: (...args) => runAutomation("getvalue", ...args),

View File

@@ -630,7 +630,7 @@ export const windowsCommands = {
// sendmessage // sendmessage
{ {
value: "quickcomposer.windows.sendmessage.inspectWindow", value: "quickcomposer.windows.sendmessage.inspectWindow",
label: "界面自动化(后台)", label: "发送控制消息",
desc: "Windows界面自动化操作", desc: "Windows界面自动化操作",
icon: "smart_button", icon: "smart_button",
isAsync: true, isAsync: true,