From a3dc6479f256104c9901854641fdc95cd3e1f45b Mon Sep 17 00:00:00 2001 From: fofolee Date: Wed, 15 Jan 2025 01:33:28 +0800 Subject: [PATCH] =?UTF-8?q?automation=E5=AE=9E=E7=8E=B0inspect=E5=85=83?= =?UTF-8?q?=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- automation.cs | 9 + plugin/lib/csharp/automation.cs | 559 ++++++++++++------ plugin/lib/csharp/index.js | 32 +- .../lib/quickcomposer/windows/automation.js | 165 ++++++ plugin/lib/quickcomposer/windows/index.js | 2 + plugin/lib/systemDialog.js | 12 +- src/components/ResultArea.vue | 4 +- src/components/composer/MultiParams.vue | 8 +- src/js/composer/commands/networkCommands.js | 120 +++- src/js/composer/commands/systemCommands.js | 59 +- src/js/composer/commands/windowsCommands.js | 226 ++++++- 11 files changed, 933 insertions(+), 263 deletions(-) create mode 100644 automation.cs create mode 100644 plugin/lib/quickcomposer/windows/automation.js diff --git a/automation.cs b/automation.cs new file mode 100644 index 0000000..9c2dc58 --- /dev/null +++ b/automation.cs @@ -0,0 +1,9 @@ + // 如果指定了过滤条件,创建一个组合条件 + if (!string.IsNullOrEmpty(filter)) + { + searchCondition = new OrCondition( + new PropertyCondition(AutomationElement.NameProperty, filter, PropertyConditionFlags.IgnoreCase), + new PropertyCondition(AutomationElement.ClassNameProperty, filter, PropertyConditionFlags.IgnoreCase), + new PropertyCondition(AutomationElement.AutomationIdProperty, filter, PropertyConditionFlags.IgnoreCase) + ); + } diff --git a/plugin/lib/csharp/automation.cs b/plugin/lib/csharp/automation.cs index 1900007..f66fc9e 100644 --- a/plugin/lib/csharp/automation.cs +++ b/plugin/lib/csharp/automation.cs @@ -9,9 +9,16 @@ using System.Web.Script.Serialization; using System.Linq; using System.Threading; using System.Windows.Forms; +using System.Drawing; public class AutomationManager { + // UIA Control Type IDs + private static class UIA_ControlTypeIds + { + public const int Window = 50032; + } + // 用于缓存找到的元素 private static Dictionary elementCache = new Dictionary(); @@ -59,6 +66,15 @@ public class AutomationManager [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern bool BlockInput(bool fBlockIt); + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + public static void Main(string[] args) { if (args.Length == 0 || args[0] == "-h" || args[0] == "--help") @@ -79,17 +95,10 @@ public class AutomationManager switch (type.ToLower()) { case "list": - // 列出所有元素 - string filter = GetArgumentValue(args, "-filter"); // 可选的过滤条件 - ListElements(args, filter); - break; - case "find": - // 查找元素 - string by = GetArgumentValue(args, "-by") ?? "name"; - string value = GetArgumentValue(args, "-value"); - string scope = GetArgumentValue(args, "-scope") ?? "children"; - FindElement(args, by, value, scope); + // 列出所有元素或查找元素 + string filter = GetArgumentValue(args, "-value") ?? GetArgumentValue(args, "-filter"); // 兼容两种参数名 + ListElements(args, filter); break; case "getinfo": @@ -135,8 +144,8 @@ public class AutomationManager case "wait": // 等待元素 - by = GetArgumentValue(args, "-by") ?? "name"; - value = GetArgumentValue(args, "-value"); + string by = GetArgumentValue(args, "-by") ?? "name"; + string value = GetArgumentValue(args, "-value"); int timeout = int.Parse(GetArgumentValue(args, "-timeout") ?? "30"); WaitForElement(args, by, value, timeout); break; @@ -171,8 +180,7 @@ public class AutomationManager case "inspect": // 生成元素识别器 - int inspectTimeout = int.Parse(GetArgumentValue(args, "-timeout") ?? "30"); - InspectElement(args, inspectTimeout); + InspectElement(args); break; default: @@ -203,6 +211,7 @@ public class AutomationManager - process 进程名 - active 当前活动窗口(默认) 不指定时默认在整个桌面范围内查找 + -background 后台操作模式,不激活窗口(可选) 元素识别参数: -value <标识值> 要查找的元素的值 @@ -215,10 +224,8 @@ public class AutomationManager 操作类型及示例: 1. inspect - 生成元素识别器 - 参数: - -timeout <超时> 等待超时时间(秒,默认30) 示例: - automation.exe -type inspect -timeout 60 + automation.exe -type inspect 说明: 运行后点击要识别的元素,将返回元素的详细信息和示例命令 @@ -465,7 +472,9 @@ public class AutomationManager var info = new Dictionary(); info.Add("name", element.Current.Name); info.Add("class", element.Current.ClassName); - info.Add("type", element.Current.ControlType.ProgrammaticName); + info.Add("controlType", element.Current.ControlType.ProgrammaticName); + info.Add("controlClass", element.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + info.Add("localizedType", element.Current.LocalizedControlType); info.Add("automationId", element.Current.AutomationId); info.Add("processId", element.Current.ProcessId); info.Add("boundingRectangle", new @@ -486,7 +495,18 @@ public class AutomationManager string identifier = GetArgumentValue(args, "-value"); string by = GetArgumentValue(args, "-by"); string pattern = GetArgumentValue(args, "-pattern") ?? "invoke"; + bool background = HasArgument(args, "-background"); + var element = GetElementByIdentifier(args, identifier, by); + + // 只在非后台操作时激活窗口 + if (!background) + { + var hwnd = new IntPtr(element.Current.NativeWindowHandle); + SetForegroundWindow(hwnd); + Thread.Sleep(50); // 等待窗口激活 + } + switch (pattern.ToLower()) { case "invoke": @@ -512,6 +532,11 @@ public class AutomationManager throw new Exception("元素不支持指定的操作模式"); } + private static bool HasArgument(string[] args, string key) + { + return args.Contains(key, StringComparer.OrdinalIgnoreCase); + } + private static string[] GetSupportedPatterns(AutomationElement element) { var patterns = new List(); @@ -527,8 +552,18 @@ public class AutomationManager { string identifier = GetArgumentValue(args, "-value"); string by = GetArgumentValue(args, "-by"); + bool background = HasArgument(args, "-background"); + var element = GetElementByIdentifier(args, identifier, by); + // 只在非后台操作时激活窗口 + if (!background) + { + var hwnd = new IntPtr(element.Current.NativeWindowHandle); + SetForegroundWindow(hwnd); + Thread.Sleep(50); // 等待窗口激活 + } + // 尝试ValuePattern var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; if (valuePattern != null) @@ -698,7 +733,7 @@ public class AutomationManager { try { - FindElement(args, by, value, "subtree"); + ListElements(args, value); Console.WriteLine("成功找到元素"); return; } @@ -734,15 +769,25 @@ public class AutomationManager private static void SendKeys(string[] args, string keys) { + string identifier = GetArgumentValue(args, "-value"); + string by = GetArgumentValue(args, "-by"); + bool background = HasArgument(args, "-background"); + + var element = GetElementByIdentifier(args, identifier, by); + + // 只在非后台操作时激活窗口 + if (!background) + { + var hwnd = new IntPtr(element.Current.NativeWindowHandle); + SetForegroundWindow(hwnd); + Thread.Sleep(50); // 等待窗口激活 + } + if (string.IsNullOrEmpty(keys)) { throw new Exception("必须指定要发送的按键"); } - string identifier = GetArgumentValue(args, "-value"); - string by = GetArgumentValue(args, "-by"); - var element = GetElementByIdentifier(args, identifier, by); - // 确保元素可以接收输入 if (!element.Current.IsKeyboardFocusable) { @@ -824,7 +869,9 @@ public class AutomationManager childInfo.Add("id", CacheElement(child)); childInfo.Add("name", child.Current.Name); childInfo.Add("class", child.Current.ClassName); - childInfo.Add("type", child.Current.ControlType.ProgrammaticName); + childInfo.Add("controlType", child.Current.ControlType.ProgrammaticName); + childInfo.Add("type", child.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + childInfo.Add("localizedType", child.Current.LocalizedControlType); childInfo.Add("automationId", child.Current.AutomationId); result.Add(childInfo); } @@ -846,7 +893,9 @@ public class AutomationManager parentInfo.Add("id", CacheElement(parent)); parentInfo.Add("name", parent.Current.Name); parentInfo.Add("class", parent.Current.ClassName); - parentInfo.Add("type", parent.Current.ControlType.ProgrammaticName); + parentInfo.Add("controlType", parent.Current.ControlType.ProgrammaticName); + parentInfo.Add("type", parent.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + parentInfo.Add("localizedType", parent.Current.LocalizedControlType); parentInfo.Add("automationId", parent.Current.AutomationId); var serializer = new JavaScriptSerializer(); @@ -891,32 +940,131 @@ public class AutomationManager { var root = GetRootElement(args); var result = new List>(); + string window = GetArgumentValue(args, "-window"); + string method = GetArgumentValue(args, "-method") ?? "active"; + string by = GetArgumentValue(args, "-by") ?? "name"; + string scope = GetArgumentValue(args, "-scope") ?? "children"; - // 获取所有元素 - var elements = root.FindAll( - TreeScope.Subtree, - Condition.TrueCondition - ); - - foreach (AutomationElement element in elements) + // 确定搜索范围 + TreeScope searchScope = TreeScope.Children; + switch (scope.ToLower()) { - // 如果指定了过滤条件,检查元素名称是否包含过滤文本 - if (!string.IsNullOrEmpty(filter) && - !element.Current.Name.Contains(filter) && - !element.Current.ClassName.Contains(filter) && - !element.Current.AutomationId.Contains(filter)) - { - continue; - } + case "descendants": + searchScope = TreeScope.Descendants; + break; + case "subtree": + searchScope = TreeScope.Subtree; + break; + } - var elementInfo = new Dictionary(); - elementInfo.Add("id", CacheElement(element)); - elementInfo.Add("name", element.Current.Name); - elementInfo.Add("class", element.Current.ClassName); - elementInfo.Add("type", element.Current.ControlType.ProgrammaticName); - elementInfo.Add("automationId", element.Current.AutomationId); - elementInfo.Add("path", GetElementPath(element)); - result.Add(elementInfo); + Condition searchCondition = Condition.TrueCondition; + + // 如果指定了过滤条件,创建一个组合条件 + if (!string.IsNullOrEmpty(filter)) + { + // 尝试直接使用ControlType + if (filter.StartsWith("ControlType.")) + { + string controlTypeName = filter.Substring("ControlType.".Length); + ControlType controlType = (ControlType)typeof(ControlType).GetField(controlTypeName).GetValue(null); + searchCondition = new PropertyCondition(AutomationElement.ControlTypeProperty, controlType); + } + else + { + switch (by.ToLower()) + { + case "name": + searchCondition = new PropertyCondition(AutomationElement.NameProperty, filter, PropertyConditionFlags.IgnoreCase); + break; + case "class": + searchCondition = new PropertyCondition(AutomationElement.ClassNameProperty, filter, PropertyConditionFlags.IgnoreCase); + break; + case "automation": + searchCondition = new PropertyCondition(AutomationElement.AutomationIdProperty, filter, PropertyConditionFlags.IgnoreCase); + break; + case "xpath": + // XPath暂不支持 + throw new Exception("XPath查找暂未实现"); + default: + // 如果没有指定查找方式,则使用组合条件 + searchCondition = new OrCondition( + new PropertyCondition(AutomationElement.NameProperty, filter, PropertyConditionFlags.IgnoreCase), + new PropertyCondition(AutomationElement.ClassNameProperty, filter, PropertyConditionFlags.IgnoreCase), + new PropertyCondition(AutomationElement.AutomationIdProperty, filter, PropertyConditionFlags.IgnoreCase) + ); + break; + } + } + } + + // 如果是按标题搜索,先找到所有匹配的窗口 + if (method.ToLower() == "title" && !string.IsNullOrEmpty(window)) + { + searchScope = TreeScope.Children; + var windows = root.FindAll(searchScope, Condition.TrueCondition); + foreach (AutomationElement win in windows) + { + if (win.Current.Name.Contains(window)) + { + // 对每个匹配的窗口进行处理 + var windowElements = win.FindAll(TreeScope.Subtree, searchCondition); + foreach (AutomationElement element in windowElements) + { + var elementInfo = new Dictionary(); + elementInfo.Add("id", CacheElement(element)); + elementInfo.Add("name", element.Current.Name); + elementInfo.Add("class", element.Current.ClassName); + elementInfo.Add("controlType", element.Current.ControlType.ProgrammaticName); + elementInfo.Add("type", element.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + elementInfo.Add("localizedType", element.Current.LocalizedControlType); + elementInfo.Add("automationId", element.Current.AutomationId); + elementInfo.Add("path", GetElementPath(element)); + + // 添加控件文本值 + try + { + var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; + if (valuePattern != null) + { + elementInfo.Add("value", valuePattern.Current.Value); + } + } + catch { } + + result.Add(elementInfo); + } + } + } + } + else + { + // 其他查找方式保持原有逻辑 + var elements = root.FindAll(searchScope, searchCondition); + foreach (AutomationElement element in elements) + { + var elementInfo = new Dictionary(); + elementInfo.Add("id", CacheElement(element)); + elementInfo.Add("name", element.Current.Name); + elementInfo.Add("class", element.Current.ClassName); + elementInfo.Add("controlType", element.Current.ControlType.ProgrammaticName); + elementInfo.Add("type", element.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + elementInfo.Add("localizedType", element.Current.LocalizedControlType); + elementInfo.Add("automationId", element.Current.AutomationId); + elementInfo.Add("path", GetElementPath(element)); + + // 添加控件文本值 + try + { + var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; + if (valuePattern != null) + { + elementInfo.Add("value", valuePattern.Current.Value); + } + } + catch { } + + result.Add(elementInfo); + } } var serializer = new JavaScriptSerializer(); @@ -963,7 +1111,15 @@ public class AutomationManager string window = GetArgumentValue(args, "-window"); string method = GetArgumentValue(args, "-method") ?? "active"; - if (string.IsNullOrEmpty(window) && method == "active") + // 如果是active方法,直接返回当前活动窗口 + if (method.ToLower() == "active") + { + IntPtr activeHandle = GetForegroundWindow(); + return AutomationElement.FromHandle(activeHandle); + } + + // 其他方法需要检查window参数 + if (string.IsNullOrEmpty(window)) { return AutomationElement.RootElement; } @@ -977,10 +1133,15 @@ public class AutomationManager case "class": // 通过窗口类名查找 - return AutomationElement.RootElement.FindFirst( + var classElements = AutomationElement.RootElement.FindAll( TreeScope.Children, new PropertyCondition(AutomationElement.ClassNameProperty, window) ); + if (classElements.Count > 0) + { + return classElements[0]; + } + break; case "process": // 通过进程名查找 @@ -991,151 +1152,199 @@ public class AutomationManager } break; - case "active": - // 获取当前活动窗口 - IntPtr activeHandle = GetForegroundWindow(); - return AutomationElement.FromHandle(activeHandle); - case "title": default: - // 通过窗口标题查找(支持模糊匹配) - var windows = AutomationElement.RootElement.FindAll( - TreeScope.Children, - Condition.TrueCondition - ); - foreach (AutomationElement win in windows) - { - if (win.Current.Name.Contains(window)) - { - return win; - } - } - break; + // 返回根元素,让调用方自己处理查找逻辑 + return AutomationElement.RootElement; } throw new Exception("找不到指定的窗口"); } - private static void FindElement(string[] args, string by, string value, string scope) + private static void InspectElement(string[] args) { - if (string.IsNullOrEmpty(value)) - { - throw new Exception("必须指定查找值 (-value)"); - } + // 创建一个半透明的全屏窗口来捕获鼠标事件 + Form overlayForm = new Form(); + overlayForm.FormBorderStyle = FormBorderStyle.None; + overlayForm.WindowState = FormWindowState.Maximized; + overlayForm.TopMost = true; + overlayForm.BackColor = Color.Black; + overlayForm.Opacity = 0.1; // 设置为淡淡的蒙版效果 + overlayForm.Cursor = Cursors.Cross; + overlayForm.ShowInTaskbar = false; - AutomationElement root = GetRootElement(args); - TreeScope treeScope = TreeScope.Children; - switch (scope.ToLower()) - { - case "descendants": - treeScope = TreeScope.Descendants; - break; - case "subtree": - treeScope = TreeScope.Subtree; - break; - } + bool completed = false; - Condition condition; - switch (by.ToLower()) - { - case "name": - condition = new PropertyCondition(AutomationElement.NameProperty, value); - break; - case "class": - condition = new PropertyCondition(AutomationElement.ClassNameProperty, value); - break; - case "automation": - condition = new PropertyCondition(AutomationElement.AutomationIdProperty, value); - break; - case "xpath": - FindElementByXPath(value); - return; - default: - throw new Exception("不支持的查找方式: " + by); - } + overlayForm.MouseClick += (sender, e) => { + if (e.Button == MouseButtons.Left) { + try { + Point clickPosition; + GetCursorPos(out clickPosition); - var elements = root.FindAll(treeScope, condition); - var result = new List>(); + // 先关闭覆盖窗口 + overlayForm.Hide(); - foreach (AutomationElement element in elements) - { - var elementInfo = new Dictionary(); - elementInfo.Add("id", CacheElement(element)); - elementInfo.Add("name", element.Current.Name); - elementInfo.Add("class", element.Current.ClassName); - elementInfo.Add("type", element.Current.ControlType.ProgrammaticName); - elementInfo.Add("automationId", element.Current.AutomationId); - result.Add(elementInfo); - } + // 等待一小段时间确保窗口完全消失 + Thread.Sleep(100); - var serializer = new JavaScriptSerializer(); - Console.Write(serializer.Serialize(result)); - } + // 获取点击位置的元素 + var element = AutomationElement.FromPoint(clickPosition.ToWindowsPoint()); - private static void InspectElement(string[] args, int timeout) - { - Console.WriteLine("请在" + timeout + "秒内点击要识别的元素..."); - - // 记录当前鼠标位置 - DateTime endTime = DateTime.Now.AddSeconds(timeout); - AutomationElement clickedElement = null; - bool wasPressed = false; - - while (DateTime.Now < endTime) - { - // 检测鼠标左键点击 - bool isPressed = (GetAsyncKeyState(0x01) & 0x8000) != 0; - if (isPressed && !wasPressed) // 鼠标按下瞬间 - { - // 获取当前鼠标位置 - Point currentPosition; - GetCursorPos(out currentPosition); - - try - { - // 从鼠标位置获取元素 - clickedElement = AutomationElement.FromPoint(currentPosition.ToWindowsPoint()); - if (clickedElement != null && clickedElement != AutomationElement.RootElement) - { - // 构建元素信息 - var elementInfo = new Dictionary(); - elementInfo.Add("id", CacheElement(clickedElement)); - elementInfo.Add("name", clickedElement.Current.Name); - elementInfo.Add("class", clickedElement.Current.ClassName); - elementInfo.Add("type", clickedElement.Current.ControlType.ProgrammaticName); - elementInfo.Add("automationId", clickedElement.Current.AutomationId); - elementInfo.Add("path", GetElementPath(clickedElement)); - elementInfo.Add("location", new { - x = clickedElement.Current.BoundingRectangle.X, - y = clickedElement.Current.BoundingRectangle.Y, - width = clickedElement.Current.BoundingRectangle.Width, - height = clickedElement.Current.BoundingRectangle.Height - }); - elementInfo.Add("patterns", GetSupportedPatterns(clickedElement)); - - // 生成示例命令 - var commands = new List(); - if (!string.IsNullOrEmpty(clickedElement.Current.Name)) - commands.Add(string.Format("automation.exe -type find -by name -value \"{0}\"", clickedElement.Current.Name)); - if (!string.IsNullOrEmpty(clickedElement.Current.AutomationId)) - commands.Add(string.Format("automation.exe -type find -by automationid -value \"{0}\"", clickedElement.Current.AutomationId)); - elementInfo.Add("commands", commands); - - var serializer = new JavaScriptSerializer(); - Console.Write(serializer.Serialize(elementInfo)); - return; + if (element != null && element != AutomationElement.RootElement) { + InspectElementInfo(element); + completed = true; } } - catch - { - // 忽略获取元素时的错误 + catch (Exception ex) { + Console.Error.WriteLine(string.Format("Error: {0}", ex.Message)); + } + finally { + overlayForm.Close(); } } + }; - wasPressed = isPressed; - Thread.Sleep(100); + overlayForm.KeyPress += (sender, e) => { + if (e.KeyChar == (char)27) { // ESC键 + overlayForm.Close(); + Environment.Exit(0); // 直接退出程序 + } + }; + + // 在新线程中显示窗口 + Thread thread = new Thread(() => { + overlayForm.Show(); + while (!completed) { + Application.DoEvents(); + Thread.Sleep(100); + } + }); + thread.SetApartmentState(ApartmentState.STA); + thread.Start(); + thread.Join(); + } + + private static void InspectElementInfo(AutomationElement element) + { + Dictionary elementInfo = new Dictionary(); + + // 基本信息 + elementInfo.Add("id", element.Current.AutomationId); + elementInfo.Add("name", element.Current.Name); + elementInfo.Add("class", element.Current.ClassName); + elementInfo.Add("controlType", element.Current.ControlType.ProgrammaticName); + elementInfo.Add("type", element.Current.ControlType.ProgrammaticName.Replace("ControlType.", "")); + elementInfo.Add("localizedType", element.Current.LocalizedControlType); + elementInfo.Add("automationId", element.Current.AutomationId); + elementInfo.Add("path", GetElementPath(element)); + elementInfo.Add("handle", element.Current.NativeWindowHandle); + + // 添加控件文本值 + try + { + var valuePattern = element.GetCurrentPattern(ValuePattern.Pattern) as ValuePattern; + if (valuePattern != null) + { + elementInfo.Add("value", valuePattern.Current.Value); + } + } + catch { } + + // 获取窗口信息 + var walker = TreeWalker.ControlViewWalker; + var parent = element; + while (parent != null && parent != AutomationElement.RootElement && parent.Current.ControlType.Id != UIA_ControlTypeIds.Window) + { + parent = walker.GetParent(parent); } - throw new Exception("操作超时"); + // 添加窗口详细信息 + if (parent != null && parent != AutomationElement.RootElement) + { + var processId = parent.Current.ProcessId; + var process = System.Diagnostics.Process.GetProcessById(processId); + elementInfo.Add("window", new + { + title = parent.Current.Name, + @class = parent.Current.ClassName, + handle = parent.Current.NativeWindowHandle, + processName = process.ProcessName, + processId = processId, + processPath = process.MainModule.FileName + }); + } + + // 添加父元素信息 + var immediateParent = walker.GetParent(element); + if (immediateParent != null && immediateParent != AutomationElement.RootElement) + { + elementInfo.Add("parent", new + { + handle = immediateParent.Current.NativeWindowHandle, + path = GetElementPath(immediateParent), + @class = immediateParent.Current.ClassName, + name = immediateParent.Current.Name, + controlType = immediateParent.Current.ControlType.ProgrammaticName, + type = immediateParent.Current.ControlType.ProgrammaticName.Replace("ControlType.", ""), + localizedType = immediateParent.Current.LocalizedControlType, + automationId = immediateParent.Current.AutomationId + }); + } + + // 位置信息 + elementInfo.Add("location", new { + x = element.Current.BoundingRectangle.X, + y = element.Current.BoundingRectangle.Y, + width = element.Current.BoundingRectangle.Width, + height = element.Current.BoundingRectangle.Height + }); + + // 支持的模式 + List patterns = new List(); + foreach (AutomationPattern pattern in element.GetSupportedPatterns()) + { + patterns.Add(pattern.ProgrammaticName); + } + elementInfo.Add("patterns", patterns); + + // 输出 JSON + var serializer = new JavaScriptSerializer(); + Console.Write(serializer.Serialize(elementInfo)); + } + + // 序列化为 JSON 字符串 + private static string JsonSerialize(object obj) + { + if (obj == null) return "null"; + + if (obj is string) + { + return string.Format("\"{0}\"", ((string)obj).Replace("\"", "\\\"")); + } + + if (obj is bool) + { + return obj.ToString().ToLower(); + } + + if (obj is int || obj is long || obj is float || obj is double) + { + return obj.ToString(); + } + + if (obj is Dictionary) + { + var dict = obj as Dictionary; + var pairs = dict.Select(kvp => string.Format("\"{0}\": {1}", kvp.Key, JsonSerialize(kvp.Value))); + return string.Format("{{{0}}}", string.Join(", ", pairs)); + } + + if (obj is System.Collections.IEnumerable) + { + var items = ((System.Collections.IEnumerable)obj).Cast().Select(item => JsonSerialize(item)); + return string.Format("[{0}]", string.Join(", ", items)); + } + + return "null"; } } diff --git a/plugin/lib/csharp/index.js b/plugin/lib/csharp/index.js index 5be5e3b..338c820 100644 --- a/plugin/lib/csharp/index.js +++ b/plugin/lib/csharp/index.js @@ -4,6 +4,8 @@ const iconv = require("iconv-lite"); const child_process = require("child_process"); const { getQuickcommandFolderFile } = require("../getQuickcommandFile"); +let currentChild = null; + const getAssemblyPath = (assembly) => { const { version } = getCscPath(); const is64bit = process.arch === "x64"; @@ -37,15 +39,9 @@ const getAssemblyPath = (assembly) => { // v3.0/v3.5 路径 path.join( - process.env["ProgramFiles(x86)"] || process.env.ProgramFiles, - "Reference Assemblies", - "Microsoft", - "Framework", - "v3.0", - assembly + ".dll" - ), - path.join( - process.env.ProgramFiles, + process.arch === "x64" + ? process.env.ProgramFiles + : process.env["ProgramFiles(x86)"], "Reference Assemblies", "Microsoft", "Framework", @@ -81,13 +77,16 @@ const getFeatureReferences = (feature) => { const formsDll = getAssemblyPath("System.Windows.Forms"); const typesDll = getAssemblyPath("UIAutomationTypes"); const baseDll = getAssemblyPath("WindowsBase"); + const drawingDll = getAssemblyPath("System.Drawing"); if (!automationDll) throw new Error("找不到UIAutomationClient.dll"); if (!formsDll) throw new Error("找不到System.Windows.Forms.dll"); if (!typesDll) throw new Error("找不到UIAutomationTypes.dll"); if (!baseDll) throw new Error("找不到WindowsBase.dll"); + if (!drawingDll) throw new Error("找不到System.Drawing.dll"); references = `/reference:"${automationDll}" /reference:"${formsDll}" ` + - `/reference:"${typesDll}" /reference:"${baseDll}" `; + `/reference:"${typesDll}" /reference:"${baseDll}" ` + + `/reference:"${drawingDll}" `; } return references; }; @@ -128,10 +127,11 @@ const buildCsharpFeature = async (feature) => { }; const getCscPath = () => { + const is64bit = process.arch === "x64"; let cscPath = path.join( process.env.WINDIR, "Microsoft.NET", - "Framework", + is64bit ? "Framework64" : "Framework", "v4.0.30319", "csc.exe" ); @@ -141,7 +141,7 @@ const getCscPath = () => { cscPath = path.join( process.env.WINDIR, "Microsoft.NET", - "Framework", + is64bit ? "Framework64" : "Framework", "v3.5", "csc.exe" ); @@ -163,13 +163,17 @@ const getCscPath = () => { */ const runCsharpFeature = async (feature, args = [], options = {}) => { return new Promise(async (reslove, reject) => { - const { alwaysBuildNewExe = false } = options; + const { alwaysBuildNewExe = window.utools.isDev(), killPrevious = true } = + options; try { const featureExePath = await getCsharpFeatureExe( feature, alwaysBuildNewExe ); - child_process.execFile( + if (killPrevious && currentChild) { + quickcommand.kill(currentChild.pid, "SIGKILL"); + } + currentChild = child_process.execFile( featureExePath, args, { diff --git a/plugin/lib/quickcomposer/windows/automation.js b/plugin/lib/quickcomposer/windows/automation.js new file mode 100644 index 0000000..94008c9 --- /dev/null +++ b/plugin/lib/quickcomposer/windows/automation.js @@ -0,0 +1,165 @@ +const { runCsharpFeature } = require("../../csharp"); + +/** + * 列出所有元素 + * @param {string} method 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {object} options 选项 + * @param {string} options.filter 过滤条件 + * @param {boolean} options.background 是否后台操作 + * @returns {object[]} 元素列表 + */ +const listElements = async function (method, window, options = {}) { + const { filter, scope } = options; + const args = ["-type", "list"]; + + args.push("-method", method); + if (method !== "active" && window) args.push("-window", window); + if (filter) args.push("-filter", filter); + if (scope) args.push("-scope", scope); + const result = await runCsharpFeature("automation", args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + try { + return JSON.parse(result); + } catch (error) { + console.error("解析元素列表失败:", error); + return null; + } +}; + +/** + * 点击元素 + * @param {string} method 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} by 查找方式:name/class/type/automationid + * @param {string} value 查找值 + * @param {object} options 选项 + * @param {string} options.pattern 点击模式:invoke/toggle + * @param {boolean} options.background 是否后台操作 + * @returns {boolean} 是否成功 + */ +const clickElement = async function (method, window, by, value, options = {}) { + const { pattern = "invoke", background = false } = options; + const args = ["-type", "click", "-by", by, "-value", value]; + + args.push("-method", method); + if (method !== "active" && window) args.push("-window", window); + if (pattern) args.push("-pattern", pattern); + if (background) args.push("-background"); + + const result = await runCsharpFeature("automation", args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +/** + * 设置元素值 + * @param {string} method 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} by 查找方式:name/class/type/automationid + * @param {string} value 查找值 + * @param {string} newValue 要设置的值 + * @param {object} options 选项 + * @param {boolean} options.background 是否后台操作 + * @returns {boolean} 是否成功 + */ +const setElementValue = async function ( + method, + window, + by, + value, + newValue, + options = {} +) { + const { background = false } = options; + const args = [ + "-type", + "setvalue", + "-by", + by, + "-value", + value, + "-newvalue", + newValue, + ]; + + args.push("-method", method); + if (method !== "active" && window) args.push("-window", window); + if (background) args.push("-background"); + + const result = await runCsharpFeature("automation", args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +/** + * 获取元素值 + * @param {string} method 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} by 查找方式:name/class/type/automationid + * @param {string} value 查找值 + * @param {object} options 选项 + * @param {boolean} options.background 是否后台操作 + * @returns {object} 元素值 + */ +const getElementValue = async function ( + method, + window, + by, + value, + options = {} +) { + const { background = false } = options; + const args = ["-type", "getvalue", "-by", by, "-value", value]; + + args.push("-method", method); + if (method !== "active" && window) args.push("-window", window); + if (background) args.push("-background"); + + const result = await runCsharpFeature("automation", args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + try { + return JSON.parse(result); + } catch (error) { + console.error("解析元素值失败:", error); + return null; + } +}; + +/** + * 检查元素 + * @param {object} options 选项 + * @param {number} options.timeout 超时时间(秒) + * @param {boolean} options.background 是否后台操作 + * @returns {object} 元素信息 + */ +const inspectElement = async function () { + const args = ["-type", "inspect"]; + + const result = await runCsharpFeature("automation", args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + try { + return JSON.parse(result); + } catch (error) { + console.error("解析元素信息失败:", error); + return null; + } +}; + +module.exports = { + listElements, + clickElement, + setElementValue, + getElementValue, + inspectElement, +}; diff --git a/plugin/lib/quickcomposer/windows/index.js b/plugin/lib/quickcomposer/windows/index.js index 99ab884..ebd0a72 100644 --- a/plugin/lib/quickcomposer/windows/index.js +++ b/plugin/lib/quickcomposer/windows/index.js @@ -6,6 +6,7 @@ const registry = require("./registry"); const service = require("./service"); const software = require("./software"); const utils = require("./utils"); +const automation = require("./automation"); module.exports = { window, @@ -16,4 +17,5 @@ module.exports = { service, software, utils, + automation, }; diff --git a/plugin/lib/systemDialog.js b/plugin/lib/systemDialog.js index d4a60f7..d9e74da 100644 --- a/plugin/lib/systemDialog.js +++ b/plugin/lib/systemDialog.js @@ -48,7 +48,7 @@ const getQuickcommandIconPath = () => { }; // 修改对话框函数,使用新的 dialog.cs -const showSystemMessageBox = async function (content, title = "") { +const showSystemMessageBox = async function (content, title = "提示") { try { const iconPath = getQuickcommandIconPath(); if (window.utools.isWindows()) { @@ -97,7 +97,7 @@ const showSystemMessageBox = async function (content, title = "") { } }; -const showSystemInputBox = async function (placeholders, title = "") { +const showSystemInputBox = async function (placeholders, title = "请输入") { if (!Array.isArray(placeholders)) { placeholders = [placeholders]; } @@ -163,7 +163,7 @@ const showSystemInputBox = async function (placeholders, title = "") { } }; -const showSystemConfirmBox = async function (content, title = "") { +const showSystemConfirmBox = async function (content, title = "请确认") { const iconPath = getQuickcommandIconPath(); if (window.utools.isMacOs()) { let iconParam = "note"; @@ -207,7 +207,7 @@ const showSystemConfirmBox = async function (content, title = "") { } }; -const showSystemButtonBox = async function (buttons, title = "") { +const showSystemButtonBox = async function (buttons, title = "请选择") { const iconPath = getQuickcommandIconPath(); if (window.utools.isMacOs()) { const itemList = buttons.map((item) => `"${item}"`).join(", "); @@ -250,7 +250,7 @@ const showSystemButtonBox = async function (buttons, title = "") { const itemsList = buttons .map((btn, index) => `"${index}" "${btn}"`) .join(" "); - const script2 = `zenity --list --title="${title}" --text="请选择:" --column="序号" --column="选项" ${itemsList} --width=400 --height=300`; + const script2 = `zenity --list --title="${title}" --column="序号" --column="选项" ${itemsList} --width=400 --height=300`; const result = await execCommand(script2); if (!result) return {}; const text = result.trim(); @@ -263,7 +263,7 @@ const showSystemButtonBox = async function (buttons, title = "") { } }; -const showSystemTextArea = async function (defaultText = "", title = "") { +const showSystemTextArea = async function (defaultText = "", title = "请输入") { const iconPath = getQuickcommandIconPath(); if (window.utools.isWindows()) { const args = [ diff --git a/src/components/ResultArea.vue b/src/components/ResultArea.vue index 2441449..7850e8e 100644 --- a/src/components/ResultArea.vue +++ b/src/components/ResultArea.vue @@ -85,7 +85,9 @@ export default { }, // 判断是否是转为表格的结果,表格结果不需要换行,第二行一般包含分隔符--- isTable() { - return this.runResult?.[0]?.split("\n")?.[1]?.includes("---"); + const result = this.runResult?.[0]; + if (typeof result !== "string") return false; + return result.split("\n")?.[1]?.includes("---"); }, }, mounted() { diff --git a/src/components/composer/MultiParams.vue b/src/components/composer/MultiParams.vue index 6e10d05..e1aafb1 100644 --- a/src/components/composer/MultiParams.vue +++ b/src/components/composer/MultiParams.vue @@ -37,13 +37,7 @@ export default defineComponent({ }, // 通用参数配置 commonConfig() { - return ( - // 过滤掉特定函数排除的参数, excludeConfig格式为[要排除的参数索引] - this.modelValue.config?.filter( - (_, index) => - !this.getSelectSubCommand()?.excludeConfig?.includes(index) - ) || [] - ); + return this.modelValue.config || []; }, // 特定函数独有参数配置,config格式和通用的config一致 subCommandConfig() { diff --git a/src/js/composer/commands/networkCommands.js b/src/js/composer/commands/networkCommands.js index b2de424..c26995e 100644 --- a/src/js/composer/commands/networkCommands.js +++ b/src/js/composer/commands/networkCommands.js @@ -53,14 +53,7 @@ export const networkCommands = { label: "URL操作", desc: "URL解析、格式化和参数处理", icon: "link", - config: [ - { - label: "URL", - component: "VariableInput", - icon: "link", - width: "auto", - }, - ], + config: [], subCommands: [ { value: "quickcomposer.network.url.parse", @@ -71,7 +64,6 @@ export const networkCommands = { value: "quickcomposer.network.url.format", label: "格式化URL", icon: "link", - excludeConfig: [0], config: [ { label: "协议", @@ -120,7 +112,6 @@ export const networkCommands = { value: "quickcomposer.network.url.parseQuery", label: "解析查询字符串", icon: "search", - excludeConfig: [0], config: [ { label: "查询字符串", @@ -133,7 +124,6 @@ export const networkCommands = { value: "quickcomposer.network.url.formatQuery", label: "格式化查询字符串", icon: "edit", - excludeConfig: [0], config: [ { label: "参数", @@ -146,7 +136,6 @@ export const networkCommands = { value: "quickcomposer.network.url.parsePath", label: "解析路径", icon: "folder_open", - excludeConfig: [0], config: [ { label: "路径", @@ -159,7 +148,6 @@ export const networkCommands = { value: "quickcomposer.network.url.parseHost", label: "解析主机名", icon: "dns", - excludeConfig: [0], config: [ { label: "主机名", @@ -173,6 +161,12 @@ export const networkCommands = { label: "获取参数", icon: "find_in_page", config: [ + { + label: "URL", + component: "VariableInput", + icon: "link", + width: "auto", + }, { label: "参数名", component: "VariableInput", @@ -186,6 +180,12 @@ export const networkCommands = { label: "添加参数", icon: "add_circle", config: [ + { + label: "URL", + component: "VariableInput", + icon: "link", + width: "auto", + }, { label: "参数名", component: "VariableInput", @@ -205,6 +205,12 @@ export const networkCommands = { label: "移除参数", icon: "remove_circle", config: [ + { + label: "URL", + component: "VariableInput", + icon: "link", + width: "auto", + }, { label: "参数名", component: "VariableInput", @@ -217,11 +223,27 @@ export const networkCommands = { value: "quickcomposer.network.url.isAbsolute", label: "检查绝对URL", icon: "check_circle", + config: [ + { + label: "URL", + component: "VariableInput", + icon: "link", + width: "auto", + }, + ], }, { value: "quickcomposer.network.url.parseComponents", label: "解析组成部分", icon: "category", + config: [ + { + label: "URL", + component: "VariableInput", + icon: "link", + width: "auto", + }, + ], }, ], }, @@ -231,20 +253,19 @@ export const networkCommands = { desc: "DNS解析和查询", icon: "dns", isAsync: true, - config: [ - { - label: "要查询的域名", - icon: "dns", - component: "VariableInput", - width: "auto", - }, - ], + config: [], subCommands: [ { label: "DNS查询", value: "quickcomposer.network.dns.lookupHost", icon: "search", config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, { label: "IP版本", icon: "settings_ethernet", @@ -269,42 +290,97 @@ export const networkCommands = { value: "quickcomposer.network.dns.resolveAll", label: "解析所有记录", icon: "all_inclusive", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveIpv4", label: "解析IPv4", icon: "filter_4", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveIpv6", label: "解析IPv6", icon: "filter_6", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveMxRecords", label: "解析MX记录", icon: "mail", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveTxtRecords", label: "解析TXT记录", icon: "text_fields", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveNsRecords", label: "解析NS记录", icon: "dns", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.resolveCnameRecords", label: "解析CNAME记录", icon: "link", + config: [ + { + label: "要查询的域名", + icon: "dns", + component: "VariableInput", + width: "auto", + }, + ], }, { value: "quickcomposer.network.dns.reverseResolve", label: "反向解析", icon: "swap_horiz", - excludeConfig: [0], config: [ { label: "IP地址", diff --git a/src/js/composer/commands/systemCommands.js b/src/js/composer/commands/systemCommands.js index a281f6a..9d2eb3c 100644 --- a/src/js/composer/commands/systemCommands.js +++ b/src/js/composer/commands/systemCommands.js @@ -308,35 +308,58 @@ export const systemCommands = { label: "路径操作", desc: "路径解析和处理", icon: "folder", - config: [ - { - label: "路径", - component: "VariableInput", - icon: "folder", - width: "auto", - }, - ], + config: [], subCommands: [ { value: "quickcomposer.system.path.normalize", label: "规范化路径", icon: "straighten", + config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, + ], }, { value: "quickcomposer.system.path.parse", label: "解析路径", icon: "account_tree", + config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, + ], }, { value: "quickcomposer.system.path.dirname", label: "获取目录名", icon: "folder", + config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, + ], }, { value: "quickcomposer.system.path.basename", label: "获取文件名", icon: "description", config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, { label: "要移除的扩展名", component: "VariableInput", @@ -349,17 +372,32 @@ export const systemCommands = { value: "quickcomposer.system.path.extname", label: "获取扩展名", icon: "extension", + config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, + ], }, { value: "quickcomposer.system.path.isAbsolute", label: "判断绝对路径", icon: "check_circle", + config: [ + { + label: "路径", + component: "VariableInput", + icon: "folder", + width: "auto", + }, + ], }, { value: "quickcomposer.system.path.join", label: "连接路径", icon: "add_link", - excludeConfig: [0], config: [ { label: "路径片段", @@ -379,7 +417,6 @@ export const systemCommands = { value: "quickcomposer.system.path.resolve", label: "解析绝对路径", icon: "assistant_direction", - excludeConfig: [0], config: [ { label: "路径片段", @@ -399,7 +436,6 @@ export const systemCommands = { value: "quickcomposer.system.path.relative", label: "计算相对路径", icon: "compare_arrows", - excludeConfig: [0], config: [ { label: "起始路径", @@ -419,7 +455,6 @@ export const systemCommands = { value: "quickcomposer.system.path.format", label: "格式化路径", icon: "format_shapes", - excludeConfig: [0], config: [ { label: "根路径", diff --git a/src/js/composer/commands/windowsCommands.js b/src/js/composer/commands/windowsCommands.js index be218f0..dcac547 100644 --- a/src/js/composer/commands/windowsCommands.js +++ b/src/js/composer/commands/windowsCommands.js @@ -104,6 +104,32 @@ const registryPaths = [ }, ]; +const searchWindowConfig = [ + { + key: "method", + label: "查找方式", + component: "q-select", + icon: "search", + width: 3, + options: [ + { label: "标题", value: "title" }, + // { label: "类名", value: "class" }, + { label: "句柄", value: "handle" }, + // { label: "进程", value: "process" }, + { label: "活动窗口", value: "active" }, + ], + defaultValue: "title", + }, + { + key: "value", + label: "窗口标题/句柄", + component: "VariableInput", + icon: "title", + width: 9, + placeholder: "标题支持模糊匹配,选择活动窗口无需输入", + }, +]; + export const windowsCommands = { label: "Win自动化", icon: "window", @@ -114,29 +140,7 @@ export const windowsCommands = { label: "窗口控制", desc: "Windows窗口操作", icon: "window", - config: [ - { - key: "method", - label: "查找方式", - component: "q-select", - icon: "search", - width: 3, - options: [ - { label: "标题", value: "title" }, - { label: "句柄", value: "handle" }, - { label: "活动窗口", value: "active" }, - ], - defaultValue: "title", - }, - { - key: "value", - label: "窗口标题/句柄", - component: "VariableInput", - icon: "title", - width: 9, - placeholder: "标题支持模糊匹配,选择活动窗口无需输入", - }, - ], + config: searchWindowConfig, subCommands: [ { value: "quickcomposer.windows.window.getWindowInfo", @@ -357,7 +361,7 @@ export const windowsCommands = { items: controlClass, }, width: 8, - placeholder: "可选,输入要过滤的控件类名或文本", + placeholder: "可选,输入要过滤的控件类型或文本", }, background: { label: "后台操作", @@ -394,7 +398,7 @@ export const windowsCommands = { width: 12, options: { control: { - label: "控件类名", + label: "控件类型", component: "VariableInput", icon: "class", options: { @@ -408,7 +412,7 @@ export const windowsCommands = { component: "VariableInput", icon: "text_fields", width: 6, - placeholder: "可选,和控件类名至少输入一个", + placeholder: "可选,和控件类型至少输入一个", }, pos: { label: "坐标", @@ -1097,5 +1101,175 @@ export const windowsCommands = { }, ], }, + { + value: "quickcomposer.windows.automation.inspectElement", + label: "UI自动化", + desc: "Windows界面自动化操作", + icon: "smart_button", + isAsync: true, + config: [], + subCommands: [ + { + value: "quickcomposer.windows.automation.inspectElement", + label: "检查元素", + icon: "search", + }, + { + value: "quickcomposer.windows.automation.listElements", + label: "列出元素", + icon: "list", + config: [ + ...searchWindowConfig, + { + component: "OptionEditor", + icon: "settings", + width: 12, + options: { + scope: { + label: "范围", + component: "q-select", + icon: "account_tree", + width: 3, + options: [ + { label: "子元素", value: "children" }, + { label: "所有后代", value: "descendants" }, + { label: "整个子树", value: "subtree" }, + ], + }, + filter: { + label: "过滤条件", + component: "VariableInput", + icon: "filter_alt", + width: 9, + placeholder: "可选,按名称/类名/ControlType/AutomationId过滤", + }, + }, + defaultValue: { + scope: "children", + }, + }, + ], + }, + { + value: "quickcomposer.windows.automation.clickElement", + label: "点击元素", + icon: "mouse", + config: [ + ...searchWindowConfig, + { + key: "by", + label: "查找方式", + component: "q-select", + icon: "search", + width: 4, + options: [ + { label: "名称", value: "name" }, + { label: "类名", value: "class" }, + { label: "类型", value: "type" }, + { label: "AutomationId", value: "automationid" }, + ], + defaultValue: "name", + }, + { + key: "value", + label: "查找值", + component: "VariableInput", + icon: "text_fields", + width: 8, + placeholder: "要点击的元素值", + }, + { + key: "pattern", + label: "点击模式", + component: "ButtonGroup", + icon: "touch_app", + width: 12, + options: [ + { label: "普通点击", value: "invoke" }, + { label: "切换状态", value: "toggle" }, + ], + defaultValue: "invoke", + }, + { + key: "background", + label: "后台操作", + component: "CheckButton", + icon: "back_hand", + width: 12, + }, + ], + }, + { + value: "quickcomposer.windows.automation.setElementValue", + label: "设置值", + icon: "edit", + config: [ + ...searchWindowConfig, + { + key: "by", + label: "查找方式", + component: "q-select", + icon: "search", + width: 4, + options: [ + { label: "名称", value: "name" }, + { label: "类名", value: "class" }, + { label: "类型", value: "type" }, + { label: "AutomationId", value: "automationid" }, + ], + defaultValue: "name", + }, + { + key: "value", + label: "查找值", + component: "VariableInput", + icon: "text_fields", + width: 8, + placeholder: "要设置值的元素", + }, + { + key: "newValue", + label: "新值", + component: "VariableInput", + icon: "edit", + width: 12, + placeholder: "要设置的新值", + }, + ], + }, + { + value: "quickcomposer.windows.automation.getElementValue", + label: "获取值", + icon: "content_paste", + outputVariable: "elementValue", + saveOutput: true, + config: [ + ...searchWindowConfig, + { + key: "by", + label: "查找方式", + component: "q-select", + icon: "search", + width: 4, + options: [ + { label: "名称", value: "name" }, + { label: "类名", value: "class" }, + { label: "类型", value: "type" }, + { label: "AutomationId", value: "automationid" }, + ], + defaultValue: "name", + }, + { + key: "value", + label: "查找值", + component: "VariableInput", + icon: "text_fields", + width: 8, + placeholder: "要获取值的元素", + }, + ], + }, + ], + }, ], };