diff --git a/plugin/lib/csharp/automation.cs b/plugin/lib/csharp/automation.cs new file mode 100644 index 0000000..e93281c --- /dev/null +++ b/plugin/lib/csharp/automation.cs @@ -0,0 +1,797 @@ +using System; +using System.Windows.Forms; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Drawing; +using System.Collections.Generic; +using System.Linq; + +public class AutomationTool +{ + #region Win32 API + [DllImport("user32.dll")] + private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); + + [DllImport("user32.dll")] + private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); + + [DllImport("user32.dll")] + private static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); + + [DllImport("user32.dll")] + private static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, int lParam); + + [DllImport("user32.dll")] + private static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, StringBuilder lParam); + + [DllImport("user32.dll")] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetFocus(); + + [DllImport("user32.dll")] + private static extern int EnumChildWindows(IntPtr hWnd, EnumWindowProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern int GetWindowTextLength(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + [DllImport("user32.dll")] + private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint); + + [DllImport("user32.dll")] + private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect); + + private const uint WM_KEYDOWN = 0x0100; + private const uint WM_KEYUP = 0x0101; + private const uint WM_CHAR = 0x0102; + private const uint WM_SETTEXT = 0x000C; + private const uint WM_LBUTTONDOWN = 0x0201; + private const uint WM_LBUTTONUP = 0x0202; + private const uint WM_RBUTTONDOWN = 0x0204; + private const uint WM_RBUTTONUP = 0x0205; + private const uint WM_LBUTTONDBLCLK = 0x0203; + + private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam); + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + [StructLayout(LayoutKind.Sequential)] + private struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + [StructLayout(LayoutKind.Sequential)] + private struct POINT + { + public int X; + public int Y; + } + + [DllImport("user32.dll")] + private static extern IntPtr GetParent(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr GetAncestor(IntPtr hwnd, int flags); + + private const int GA_ROOT = 2; + + // 添加工具栏相关的常量和结构体 + private const uint TB_BUTTONCOUNT = 0x0418; + private const uint TB_GETBUTTON = 0x0417; + private const uint TB_GETBUTTONTEXT = 0x042D; + private const uint TB_GETITEMRECT = 0x041D; + + [StructLayout(LayoutKind.Sequential)] + private struct TBBUTTON + { + public int iBitmap; + public int idCommand; + public byte fsState; + public byte fsStyle; + public byte bReserved1; + public byte bReserved2; + public IntPtr dwData; + public IntPtr iString; + } + + // 添加 SendMessage 重载 + [DllImport("user32.dll")] + private static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, ref TBBUTTON lParam); + + [DllImport("user32.dll")] + private static extern bool SendMessage(IntPtr hWnd, uint Msg, int wParam, ref RECT lParam); + #endregion + + public static void Main(string[] args) + { + if (args.Length == 0 || args[0] == "-h" || args[0] == "--help") + { + ShowHelp(); + return; + } + + string type = GetArgumentValue(args, "-type"); + if (string.IsNullOrEmpty(type)) + { + Console.Error.WriteLine("Error: 必须指定操作类型 (-type)"); + return; + } + + string action = GetArgumentValue(args, "-action"); + string value = GetArgumentValue(args, "-value"); + string window = GetArgumentValue(args, "-window"); + string control = GetArgumentValue(args, "-control"); + string filter = GetArgumentValue(args, "-filter"); + string pos = GetArgumentValue(args, "-pos"); + bool background = HasArgument(args, "-background"); + + try + { + switch (type.ToLower()) + { + case "keyboard": + HandleKeyboardOperation(args); + break; + case "mouse": + HandleMouseOperation(args); + break; + case "inspect": + HandleInspectOperation(args); + break; + default: + Console.WriteLine("Error: 不支持的操作类型"); + break; + } + } + catch (Exception ex) + { + Console.WriteLine(string.Format("Error: {0}", ex.Message)); + } + } + + private static List FindTargetWindows(string window) + { + List targetWindows = new List(); + if (string.IsNullOrEmpty(window)) + { + targetWindows.Add(GetForegroundWindow()); + } + else + { + // 查找所有匹配的窗口 + EnumWindows((hwnd, param) => + { + StringBuilder title = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + string windowTitle = title.ToString(); + + if (!string.IsNullOrEmpty(windowTitle) && + windowTitle.IndexOf(window, StringComparison.OrdinalIgnoreCase) >= 0) + { + targetWindows.Add(hwnd); + } + return true; + }, IntPtr.Zero); + + if (targetWindows.Count == 0) + { + Console.WriteLine("Error: 未找到匹配的窗口"); + return targetWindows; + } + + // 如果找到多个窗口,输出所有窗口信息 + if (targetWindows.Count > 1) + { + Console.WriteLine("找到 {0} 个匹配窗口:", targetWindows.Count); + foreach (IntPtr hwnd in targetWindows) + { + StringBuilder title = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + Console.WriteLine("0x{0:X} - {1}", hwnd.ToInt64(), title); + } + } + } + + return targetWindows; + } + + private static void HandleKeyboardOperation(string[] args) + { + string window = GetArgumentValue(args, "-window"); + string control = GetArgumentValue(args, "-control"); + string action = GetArgumentValue(args, "-action"); + string value = GetArgumentValue(args, "-value"); + bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false"); + + if (string.IsNullOrEmpty(action)) + { + Console.WriteLine("Error: keyboard操作需要指定 -action 参数"); + return; + } + + var targetWindows = FindTargetWindows(window); + if (targetWindows.Count == 0) + { + return; + } + + foreach (IntPtr hWnd in targetWindows) + { + IntPtr targetHandle = hWnd; + + // 如果指定了控件,递归查找控件句柄 + if (!string.IsNullOrEmpty(control)) + { + IntPtr hControl = FindControl(hWnd, control); + if (hControl != IntPtr.Zero) + { + targetHandle = hControl; + } + else + { + Console.WriteLine(string.Format("Warning: 在窗口 0x{0:X} 中未找到指定控件", hWnd.ToInt64())); + continue; + } + } + + // 只在非后台操作时激活窗口 + if (!background) + { + SetForegroundWindow(hWnd); + Thread.Sleep(50); // 等待窗口激活 + } + + switch (action.ToLower()) + { + case "keys": + if (string.IsNullOrEmpty(value)) + { + Console.WriteLine("Error: 发送按键需要指定 -value 参数"); + return; + } + SendKeys(targetHandle, value); + break; + + case "text": + if (string.IsNullOrEmpty(value)) + { + Console.WriteLine("Error: 发送文本需要指定 -value 参数"); + return; + } + SendText(targetHandle, value); + break; + + default: + Console.WriteLine("Error: 不支持的keyboard操作类型"); + break; + } + } + } + + private static void HandleMouseOperation(string[] args) + { + string window = GetArgumentValue(args, "-window"); + string control = GetArgumentValue(args, "-control"); + string controlText = GetArgumentValue(args, "-text"); + string action = GetArgumentValue(args, "-action"); + string position = GetArgumentValue(args, "-pos"); + bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false"); + + if (string.IsNullOrEmpty(action)) + { + Console.WriteLine("Error: mouse操作需要指定 -action 参数"); + return; + } + + var targetWindows = FindTargetWindows(window); + if (targetWindows.Count == 0) + { + return; + } + + foreach (IntPtr hWnd in targetWindows) + { + IntPtr targetHandle = hWnd; + + // 如果指定了控件类名和文本,查找匹配的控件 + if (!string.IsNullOrEmpty(control) || !string.IsNullOrEmpty(controlText)) + { + targetHandle = FindControlByTextAndClass(hWnd, controlText, control); + if (targetHandle == IntPtr.Zero) + { + Console.WriteLine(string.Format("Warning: 在窗口 0x{0:X} 中未找到指定控件", hWnd.ToInt64())); + continue; + } + } + + // 只在非后台操作时激活窗口 + if (!background) + { + SetForegroundWindow(hWnd); + Thread.Sleep(50); + } + + // 获取点击坐标 + int x = 0, y = 0; + if (!string.IsNullOrEmpty(position)) + { + // 使用指定坐标 + string[] pos = position.Split(','); + if (pos.Length == 2) + { + x = int.Parse(pos[0]); + y = int.Parse(pos[1]); + } + } + else + { + // 如果没有指定坐标,点击控件中心 + RECT rect; + if (GetWindowRect(targetHandle, out rect)) + { + x = (rect.Right - rect.Left) / 2; + y = (rect.Bottom - rect.Top) / 2; + } + } + + int lParam = (y << 16) | (x & 0xFFFF); + + switch (action.ToLower()) + { + case "click": + SendMessage(targetHandle, WM_LBUTTONDOWN, 0, lParam); + SendMessage(targetHandle, WM_LBUTTONUP, 0, lParam); + break; + + case "rightclick": + SendMessage(targetHandle, WM_RBUTTONDOWN, 0, lParam); + SendMessage(targetHandle, WM_RBUTTONUP, 0, lParam); + break; + + case "doubleclick": + SendMessage(targetHandle, WM_LBUTTONDOWN, 0, lParam); + SendMessage(targetHandle, WM_LBUTTONUP, 0, lParam); + SendMessage(targetHandle, WM_LBUTTONDBLCLK, 0, lParam); + SendMessage(targetHandle, WM_LBUTTONUP, 0, lParam); + break; + + default: + Console.WriteLine("Error: 不支持的mouse操作类型"); + break; + } + } + } + + private static void SendKeys(IntPtr hWnd, string keys) + { + // 解析按键组合 + string[] keyArray = keys.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string keyCombo in keyArray) + { + string[] modifiers = keyCombo.Trim().Split('+'); + List modifierKeys = new List(); + byte mainKey = 0; + + // 处理每个按键 + for (int i = 0; i < modifiers.Length; i++) + { + byte vKey = GetVirtualKeyCode(modifiers[i].Trim()); + if (i < modifiers.Length - 1) + { + modifierKeys.Add(vKey); + } + else + { + mainKey = vKey; + } + } + + // 按下修饰键 + foreach (byte modifier in modifierKeys) + { + PostMessage(hWnd, WM_KEYDOWN, modifier, 0); + } + + // 按下主键 + if (mainKey > 0) + { + PostMessage(hWnd, WM_KEYDOWN, mainKey, 0); + Thread.Sleep(10); + PostMessage(hWnd, WM_KEYUP, mainKey, 0); + } + + // 释放修饰键 + for (int i = modifierKeys.Count - 1; i >= 0; i--) + { + PostMessage(hWnd, WM_KEYUP, modifierKeys[i], 0); + } + + // 如果有多个按键组合,等待一下 + if (keyArray.Length > 1) + { + Thread.Sleep(50); + } + } + } + + private static void SendText(IntPtr hWnd, string text) + { + StringBuilder sb = new StringBuilder(text); + SendMessage(hWnd, WM_SETTEXT, 0, sb); + } + + private static string GetArgumentValue(string[] args, string key) + { + for (int i = 0; i < args.Length - 1; i++) + { + if (args[i].Equals(key, StringComparison.OrdinalIgnoreCase)) + { + return args[i + 1]; + } + } + return null; + } + + private static bool HasArgument(string[] args, string key) + { + return Array.Exists(args, arg => arg.Equals(key, StringComparison.OrdinalIgnoreCase)); + } + + private static byte GetVirtualKeyCode(string key) + { + switch (key.ToLower()) + { + case "ctrl": return 0x11; + case "alt": return 0x12; + case "shift": return 0x10; + case "win": return 0x5B; + case "enter": return 0x0D; + case "tab": return 0x09; + case "esc": return 0x1B; + case "space": return 0x20; + case "backspace": return 0x08; + case "delete": return 0x2E; + default: + if (key.Length == 1) + { + return (byte)key.ToUpper()[0]; + } + throw new ArgumentException(string.Format("不支持的按键: {0}", key)); + } + } + + private static IntPtr FindControlRecursive(IntPtr parentHandle, string targetClassName) + { + if (string.IsNullOrEmpty(targetClassName)) + return IntPtr.Zero; + + IntPtr foundHandle = IntPtr.Zero; + List childHandles = new List(); + + // 枚举所有子窗口 + EnumWindowProc childProc = new EnumWindowProc((handle, param) => + { + // 获取类名 + StringBuilder classNameBuffer = new StringBuilder(256); + GetClassName(handle, classNameBuffer, classNameBuffer.Capacity); + + // 检查是否匹配 + if (classNameBuffer.ToString().Equals(targetClassName, StringComparison.OrdinalIgnoreCase)) + { + foundHandle = handle; + return false; // 找到后停止枚举 + } + + childHandles.Add(handle); + return true; + }); + + EnumChildWindows(parentHandle, childProc, IntPtr.Zero); + + // 如果在当前层级没找到,递归查找子窗口 + if (foundHandle == IntPtr.Zero) + { + foreach (IntPtr childHandle in childHandles) + { + foundHandle = FindControlRecursive(childHandle, targetClassName); + if (foundHandle != IntPtr.Zero) + break; + } + } + + return foundHandle; + } + + // 修改 HandleKeyboardOperation 和 HandleMouseOperation 中查找控件的部分 + private static IntPtr FindControl(IntPtr parentHandle, string controlClass) + { + // 先尝试直接查找 + IntPtr hControl = FindWindowEx(parentHandle, IntPtr.Zero, controlClass, null); + if (hControl != IntPtr.Zero) + return hControl; + + // 如果直接查找失败,进行递归查找 + return FindControlRecursive(parentHandle, controlClass); + } + + private static IntPtr FindControlByTextAndClass(IntPtr parentHandle, string controlText, string className) + { + if (string.IsNullOrEmpty(controlText) && string.IsNullOrEmpty(className)) + return IntPtr.Zero; + + List matchedControls = new List(); + Queue searchQueue = new Queue(); + searchQueue.Enqueue(parentHandle); + + while (searchQueue.Count > 0) + { + IntPtr currentHandle = searchQueue.Dequeue(); + List children = new List(); + + // 枚举当前层级的子窗口 + EnumWindowProc childProc = new EnumWindowProc((handle, param) => + { + bool match = true; + + // 检查类名(如果指定) + if (!string.IsNullOrEmpty(className)) + { + StringBuilder classNameBuffer = new StringBuilder(256); + GetClassName(handle, classNameBuffer, classNameBuffer.Capacity); + if (!className.Equals(classNameBuffer.ToString(), StringComparison.OrdinalIgnoreCase)) + { + match = false; + } + } + + // 检查控件文本(如果指定) + if (match && !string.IsNullOrEmpty(controlText)) + { + StringBuilder textBuffer = new StringBuilder(256); + GetWindowText(handle, textBuffer, textBuffer.Capacity); + string windowText = textBuffer.ToString(); + if (!windowText.Contains(controlText)) + { + match = false; + } + } + + // 检查控件是否可见 + if (match && !IsWindowVisible(handle)) + { + match = false; + } + + if (match) + { + matchedControls.Add(handle); + } + + // 将子窗口加入搜索队列 + children.Add(handle); + return true; + }); + + EnumChildWindows(currentHandle, childProc, IntPtr.Zero); + + // 将所有子窗口加入搜索队列 + foreach (IntPtr child in children) + { + searchQueue.Enqueue(child); + } + } + + if (matchedControls.Count == 0) + { + return IntPtr.Zero; + } + else if (matchedControls.Count > 1) + { + Console.WriteLine("Warning: 找到多个匹配控件:"); + foreach (IntPtr handle in matchedControls) + { + StringBuilder text = new StringBuilder(256); + GetWindowText(handle, text, text.Capacity); + StringBuilder classBuffer = new StringBuilder(256); + GetClassName(handle, classBuffer, classBuffer.Capacity); + Console.WriteLine(string.Format("0x{0:X} - Class: {1}, Text: {2}", + handle.ToInt64(), classBuffer, text)); + } + } + + return matchedControls[0]; + } + + private static void HandleInspectOperation(string[] args) + { + string window = GetArgumentValue(args, "-window"); + string filter = GetArgumentValue(args, "-filter"); + bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false"); + + var targetWindows = FindTargetWindows(window); + if (targetWindows.Count == 0) + { + return; + } + + // 输出所有匹配窗口的信息 + StringBuilder json = new StringBuilder(); + json.Append("["); + bool firstWindow = true; + + foreach (IntPtr hWnd in targetWindows) + { + // 只在非后台操作时激活窗口 + if (!background) + { + SetForegroundWindow(hWnd); + Thread.Sleep(50); + } + + if (!firstWindow) + { + json.Append(","); + } + firstWindow = false; + + json.Append(GetControlsTree(hWnd, filter)); + } + + json.Append("]"); + Console.WriteLine(json.ToString()); + } + + private static string GetControlsTree(IntPtr parentHandle, string filter, int depth = 0) + { + if (parentHandle == IntPtr.Zero) + return "{}"; + + StringBuilder json = new StringBuilder(); + json.Append("{"); + + // 获取窗口信息 + StringBuilder title = new StringBuilder(256); + StringBuilder className = new StringBuilder(256); + GetWindowText(parentHandle, title, title.Capacity); + GetClassName(parentHandle, className, className.Capacity); + + // 获取窗口位置和大小 + RECT windowRect; + GetWindowRect(parentHandle, out windowRect); + + bool isVisible = IsWindowVisible(parentHandle); + + // 检查当前节点是否匹配过滤条件 + bool matchFilter = true; + if (!string.IsNullOrEmpty(filter)) + { + matchFilter = + title.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 || + className.ToString().IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0; + } + + // 添加节点信息 + json.AppendFormat( + "\"handle\":\"0x{0:X}\",\"class\":\"{1}\",\"text\":\"{2}\",\"visible\":{3},\"location\":{{\"x\":{4},\"y\":{5},\"width\":{6},\"height\":{7}}},\"matched\":{8},\"children\":[", + parentHandle.ToInt64(), + className.ToString().Replace("\"", "\\\""), + title.ToString().Replace("\"", "\\\""), + isVisible.ToString().ToLower(), + windowRect.Left, + windowRect.Top, + windowRect.Right - windowRect.Left, + windowRect.Bottom - windowRect.Top, + matchFilter.ToString().ToLower() + ); + + // 递归获取子控件树 + List childJsons = new List(); + + EnumChildWindows(parentHandle, (hwnd, param) => + { + string childJson = GetControlsTree(hwnd, filter, depth + 1); + if (!string.IsNullOrEmpty(childJson) && childJson != "{}") + { + childJsons.Add(childJson); + } + return true; + }, IntPtr.Zero); + + // 添加子节点JSON + if (childJsons.Count > 0) + { + json.Append(string.Join(",", childJsons)); + } + + json.Append("]}"); + + return json.ToString(); + } + + private static void ShowHelp() + { + string help = @" +Windows 界面自动化工具使用说明 +========================== + +基本语法: +automation.exe -type <操作类型> [参数...] + +操作类型: +-------- +1. keyboard - 键盘操作 +2. mouse - 鼠标操作 +3. inspect - 获取控件树 + +通用参数: +-------- +-window 窗口标题或句柄(支持模糊匹配) +-control 控件类名 +-background 后台操作,不激活窗口,默认激活 + +键盘操作参数: +----------- +-action 操作类型:keys(按键)或text(文本) +-value 要发送的按键或文本内容 + +鼠标操作参数: +----------- +-action 操作类型:click(单击)、doubleclick(双击)、rightclick(右键) +-text 控件文本 +-pos 点击坐标(x,y) + +控件树参数: +--------- +-filter 过滤条件(控件类名或文本) + +使用示例: +-------- +1. 发送按键到指定窗口: + automation.exe -type keyboard -action keys -window ""记事本"" -value ""ctrl+a"" + +2. 发送文本到指定控件: + automation.exe -type keyboard -action text -window ""记事本"" -control ""Edit"" -value ""Hello World"" + +3. 点击指定控件: + automation.exe -type mouse -action click -window ""记事本"" -control ""Button"" -text ""确定"" + +4. 后台发送文本: + automation.exe -type keyboard -action text -window ""记事本"" -value ""Hello"" -background + +5. 获取窗口控件树: + automation.exe -type inspect -window ""记事本"" -filter ""button"" + +注意事项: +-------- +1. 窗口标题支持模糊匹配 +2. 控件类名需要完全匹配 +3. 后台操作可能会影响某些程序的响应 +4. 建议先用inspect获取正确的控件信息再进行操作 +"; + Console.WriteLine(help); + } +} diff --git a/plugin/lib/csharp/dialog.cs b/plugin/lib/csharp/dialog.cs index a1f50aa..bb455eb 100644 --- a/plugin/lib/csharp/dialog.cs +++ b/plugin/lib/csharp/dialog.cs @@ -790,4 +790,96 @@ public class DialogGenerator public int Bottom; } } + + private static void ShowHelp() + { + string help = @" +Windows 对话框生成工具使用说明 +========================== + +基本语法: +dialog.exe -type <对话框类型> [参数...] + +对话框类型: +---------- +1. message - 消息对话框,显示一条消息 +2. input - 输入对话框,支持多个输入框 +3. confirm - 确认对话框,带确定和取消按钮 +4. buttons - 按钮对话框,支持多个自定义按钮 +5. textarea - 文本区域对话框,用于编辑大段文本 + +通用参数: +-------- +-type 对话框类型(必需) + 可选值:message, input, confirm, buttons, textarea + +-title 对话框标题(必需) + 示例:-title ""提示"" + +-content 对话框内容 + - message/confirm/textarea:显示的文本内容 + - input:输入框提示文本,多个输入框用|||||分隔 + - buttons:按钮文本,多个按钮用|||||分隔 + +-iconpath 对话框图标路径(可选) + 示例:-iconpath ""D:\icons\app.ico"" + +使用示例: +-------- +1. 显示消息对话框: + dialog.exe -type message -title ""提示"" -content ""操作已完成"" + +2. 显示输入对话框(单个输入): + dialog.exe -type input -title ""输入"" -content ""请输入用户名:"" + +3. 显示输入对话框(多个输入): + dialog.exe -type input -title ""用户信息"" -content ""用户名:|||||密码:|||||邮箱:"" + +4. 显示确认对话框: + dialog.exe -type confirm -title ""确认"" -content ""确定要删除这个文件吗?"" + +5. 显示按钮对话框: + dialog.exe -type buttons -title ""选择操作"" -content ""保存|||||不保存|||||取消"" + +6. 显示文本区域对话框: + dialog.exe -type textarea -title ""编辑文本"" -content ""在这里输入内容..."" + +返回值: +------ +1. message对话框: + 无返回值 + +2. input对话框: + 返回JSON数组,包含所有输入框的值 + 示例:[""user"",""123456"",""user@email.com""] + 用户取消:[] + +3. confirm对话框: + 确定:true + 取消:false + 关闭窗口:{} + +4. buttons对话框: + 返回JSON对象,包含按钮id和文本 + 示例:{""id"":0,""text"":""保存""} + 关闭窗口:{} + +5. textarea对话框: + 返回编辑后的文本内容 + 取消:空字符串 + +注意事项: +-------- +1. 包含空格的参数值需要用引号括起来 +2. 多个输入框或按钮的文本用|||||(5个竖线)分隔 +3. 所有对话框都支持自定义图标(使用-iconpath参数) +4. 对话框会自动适应DPI缩放 +5. 所有对话框都支持文本选择和复制 + +更多信息: +-------- +遇到问题请查看错误信息,错误信息会提供详细的原因说明。 +"; + Console.WriteLine(help); + } } diff --git a/plugin/lib/quickcommand.js b/plugin/lib/quickcommand.js index 4a555a6..91dcce2 100644 --- a/plugin/lib/quickcommand.js +++ b/plugin/lib/quickcommand.js @@ -208,7 +208,14 @@ const quickcommand = { if (err) return reject(err.toString()); // 添加命令行参数 const argsStr = - args.length > 0 ? " " + args.map((arg) => `"${arg}"`).join(" ") : ""; + args.length > 0 + ? " " + + args + .map((arg) => + arg.startsWith("-") ? arg : `"${arg}"` + ) + .join(" ") + : ""; child_process.exec( `${cscPath} /nologo /out:${tempBuildFile} ${tempCsharpFile} && ${tempBuildFile}${argsStr}`, { diff --git a/plugin/lib/quickcomposer/windows/automation.js b/plugin/lib/quickcomposer/windows/automation.js new file mode 100644 index 0000000..5f92fdf --- /dev/null +++ b/plugin/lib/quickcomposer/windows/automation.js @@ -0,0 +1,129 @@ +const fs = require("fs"); +const path = require("path"); +const quickcommand = require("../../quickcommand"); + +// 读取 automation.cs 模板 +const automationTemplate = fs.readFileSync( + path.join(__dirname, "..", "..", "csharp", "automation.cs"), + "utf8" +); + +/** + * 键盘操作 + * @param {string} windowType 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} keys 键盘按键 + * @param {object} options 选项 + * @param {string} options.control 控件类名 + * @param {boolean} options.background 是否后台操作 + * @returns {boolean} 是否成功 + */ +const sendKeys = async function (windowType, window, keys, options = {}) { + const { control, background = false } = options; + const args = ["-type", "keyboard", "-action", "keys", "-value", keys]; + + if (windowType !== "active" && window) args.push("-window", window); + if (control) args.push("-control", control); + args.push("-background", background.toString()); + + const result = await quickcommand.runCsharp(automationTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +/** + * 发送文本 + * @param {string} windowType 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} text 文本 + * @param {object} options 选项 + * @param {string} options.control 控件类名 + * @param {boolean} options.background 是否后台操作 + * @returns {boolean} 是否成功 + */ +const sendText = async function (windowType, window, text, options = {}) { + const { control, background = false } = options; + const args = ["-type", "keyboard", "-action", "text", "-value", text]; + + if (windowType !== "active" && window) args.push("-window", window); + if (control) args.push("-control", control); + args.push("-background", background.toString()); + + const result = await quickcommand.runCsharp(automationTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +/** + * 鼠标点击 + * @param {string} windowType 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {string} action 动作:click/doubleClick/rightClick + * @param {object} options 选项 + * @param {string} options.control 控件类名 + * @param {string} options.text 控件文本 + * @param {string} options.pos 点击位置:x,y + * @param {boolean} options.background 是否后台操作 + * @returns {boolean} 是否成功 + */ +const click = async function ( + windowType, + window, + action = "click", + options = {} +) { + const { control, text, pos, background = false } = options; + const args = ["-type", "mouse", "-action", action]; + + if (windowType !== "active" && window) args.push("-window", window); + if (control) args.push("-control", control); + if (text) args.push("-text", text); + if (pos) args.push("-pos", pos); + args.push("-background", background.toString()); + + const result = await quickcommand.runCsharp(automationTemplate, args); + if (result && result.startsWith("Error:")) { + throw new Error(result.substring(7)); + } + return true; +}; + +/** + * 获取窗口控件树 + * @param {string} windowType 窗口类型:title/handle/active + * @param {string} window 窗口标题/句柄 + * @param {object} options 选项 + * @param {string} options.filter 过滤条件 + * @param {boolean} options.background 是否后台操作 + * @returns {object} 控件树 + */ +const inspectWindow = async function (windowType, window, options = {}) { + const { filter, background = false } = options; + const args = ["-type", "inspect"]; + + if (windowType !== "active" && window) args.push("-window", window); + if (filter) args.push("-filter", filter); + args.push("-background", background.toString()); + + const result = await quickcommand.runCsharp(automationTemplate, 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 = { + sendKeys, + sendText, + click, + inspectWindow, +}; diff --git a/plugin/lib/quickcomposer/windows/index.js b/plugin/lib/quickcomposer/windows/index.js index e5a7774..2435ddf 100644 --- a/plugin/lib/quickcomposer/windows/index.js +++ b/plugin/lib/quickcomposer/windows/index.js @@ -1,7 +1,9 @@ const window = require("./window"); const message = require("./message"); +const automation = require("./automation"); module.exports = { window, message, + automation, }; diff --git a/plugin/lib/quickcomposer/windows/window.js b/plugin/lib/quickcomposer/windows/window.js index af16097..8f25ba0 100644 --- a/plugin/lib/quickcomposer/windows/window.js +++ b/plugin/lib/quickcomposer/windows/window.js @@ -489,7 +489,11 @@ async function getWindowInfo(method, value) { `; const result = await quickcommand.runCsharp(script); - return JSON.parse(result); + try { + return JSON.parse(result); + } catch (error) { + return {}; + } } /** diff --git a/src/js/composer/commands/windowsCommands.js b/src/js/composer/commands/windowsCommands.js index d908b70..98d0b69 100644 --- a/src/js/composer/commands/windowsCommands.js +++ b/src/js/composer/commands/windowsCommands.js @@ -1,3 +1,50 @@ +const controlClass = [ + // 基础控件 + { value: "Button", label: "按钮 (Button)" }, + { value: "Edit", label: "编辑框 (Edit)" }, + { value: "Static", label: "静态文本 (Static)" }, + { value: "ComboBox", label: "下拉框 (ComboBox)" }, + { value: "ListBox", label: "列表框 (ListBox)" }, + { value: "CheckBox", label: "复选框 (CheckBox)" }, + { value: "RadioButton", label: "单选框 (RadioButton)" }, + + // 常见对话框控件 + { value: "SysListView32", label: "列表视图 (SysListView32)" }, + { value: "SysTreeView32", label: "树形视图 (SysTreeView32)" }, + { value: "SysTabControl32", label: "选项卡 (SysTabControl32)" }, + { value: "msctls_progress32", label: "进度条 (msctls_progress32)" }, + { value: "msctls_trackbar32", label: "滑块 (msctls_trackbar32)" }, + { value: "msctls_updown32", label: "数字调节器 (msctls_updown32)" }, + + // 文件对话框相关 + { value: "DirectUIHWND", label: "文件浏览器 (DirectUIHWND)" }, + { value: "ToolbarWindow32", label: "工具栏 (ToolbarWindow32)" }, + { value: "ComboBoxEx32", label: "扩展下拉框 (ComboBoxEx32)" }, + + // 常见应用程序控件 + { value: "RICHEDIT50W", label: "富文本编辑框 (RICHEDIT50W)" }, + { value: "Scintilla", label: "代码编辑器 (Scintilla)" }, + { value: "WebView2", label: "Edge浏览器 (WebView2)" }, + { + value: "Chrome_RenderWidgetHostHWND", + label: "Chrome渲染 (Chrome_RenderWidgetHostHWND)", + }, + + // 系统控件 + { value: "Shell_TrayWnd", label: "任务栏 (Shell_TrayWnd)" }, + { value: "TrayNotifyWnd", label: "通知区域 (TrayNotifyWnd)" }, + { value: "ReBarWindow32", label: "工具条容器 (ReBarWindow32)" }, + { value: "TaskListThumbnailWnd", label: "任务预览 (TaskListThumbnailWnd)" }, + + // 通用容器 + { value: "Window", label: "窗口 (Window)" }, + { value: "Dialog", label: "对话框 (Dialog)" }, + { value: "#32770", label: "标准对话框 (#32770)" }, + { value: "MDIClient", label: "MDI客户区 (MDIClient)" }, + { value: "ScrollBar", label: "滚动条 (ScrollBar)" }, + { value: "GroupBox", label: "分组框 (GroupBox)" }, +]; + export const windowsCommands = { label: "Win自动化", icon: "window", @@ -202,9 +249,10 @@ export const windowsCommands = { isAsync: true, }, { - value: "quickcomposer.windows.message.sendMouseClick", - label: "发送消息", - icon: "send", + value: "quickcomposer.windows.automation.inspectWindow", + label: "界面自动化", + desc: "Windows界面自动化操作", + icon: "smart_button", isAsync: true, config: [ { @@ -231,191 +279,177 @@ export const windowsCommands = { ], subCommands: [ { - value: "quickcomposer.windows.message.sendMouseClick", - label: "鼠标点击", + value: "quickcomposer.windows.automation.inspectWindow", + label: "获取控件树", + icon: "account_tree", + outputVariable: "controlsTree", + saveOutput: true, + config: [ + { + key: "options", + component: "OptionEditor", + width: 12, + options: { + filter: { + label: "控件过滤", + component: "VariableInput", + icon: "filter_alt", + options: { + items: controlClass, + }, + width: 8, + placeholder: "可选,输入要过滤的控件类名或文本", + }, + background: { + label: "后台操作", + component: "CheckButton", + icon: "back_hand", + width: 4, + }, + }, + defaultValue: { + background: true, + }, + }, + ], + }, + { + value: "quickcomposer.windows.automation.click", + label: "点击控件", icon: "mouse", config: [ { - key: "type", - defaultValue: "click", - hidden: true, - }, - { - key: "button", - label: "按键", - component: "q-select", - icon: "mouse", - width: 4, + key: "action", + component: "ButtonGroup", + width: 12, options: [ - { label: "左键", value: "left" }, - { label: "右键", value: "right" }, - { label: "中键", value: "middle" }, + { label: "单击", value: "click" }, + { label: "双击", value: "doubleclick" }, + { label: "右键", value: "rightclick" }, ], - defaultValue: "left", + defaultValue: "click", }, { - key: "x", - label: "X坐标", - component: "NumberInput", - icon: "arrow_right", - width: 4, - defaultValue: 0, - }, - { - key: "y", - label: "Y坐标", - component: "NumberInput", - icon: "arrow_drop_down", - width: 4, - defaultValue: 0, + key: "options", + component: "OptionEditor", + width: 12, + options: { + control: { + label: "控件类名", + component: "VariableInput", + icon: "class", + options: { + items: controlClass, + }, + width: 6, + placeholder: "可选,和文本至少输入一个", + }, + text: { + label: "控件文本", + component: "VariableInput", + icon: "text_fields", + width: 6, + placeholder: "可选,和控件类名至少输入一个", + }, + pos: { + label: "坐标", + component: "VariableInput", + icon: "place", + width: 6, + placeholder: "可选,格式:x,y", + }, + background: { + label: "后台操作", + component: "CheckButton", + icon: "back_hand", + width: 6, + }, + }, + defaultValue: { + background: true, + }, }, ], }, { - value: "quickcomposer.windows.message.sendKeyPress", - label: "键盘按键", + value: "quickcomposer.windows.automation.sendText", + label: "发送文本", icon: "keyboard", config: [ - { - key: "type", - defaultValue: "key", - hidden: true, - }, - { - key: "keyCode", - label: "按键码", - component: "NumberInput", - icon: "keyboard", - width: 6, - defaultValue: 0, - }, - { - key: "ctrl", - label: "Ctrl", - component: "Switch", - width: 3, - defaultValue: false, - }, - { - key: "alt", - label: "Alt", - component: "Switch", - width: 3, - defaultValue: false, - }, - { - key: "shift", - label: "Shift", - component: "Switch", - width: 3, - defaultValue: false, - }, - { - key: "hold", - label: "按住", - component: "Switch", - width: 3, - defaultValue: false, - }, - ], - }, - { - value: "quickcomposer.windows.message.sendText", - label: "文本输入", - icon: "text_fields", - config: [ - { - key: "type", - defaultValue: "text", - hidden: true, - }, { key: "text", label: "文本内容", component: "VariableInput", icon: "text_fields", width: 12, + placeholder: "要发送的文本内容", + }, + { + key: "options", + component: "OptionEditor", + width: 12, + options: { + control: { + label: "目标控件", + component: "VariableInput", + options: { + items: controlClass, + }, + icon: "class", + width: 8, + placeholder: "可选,目标控件的类名", + }, + background: { + label: "后台操作", + component: "CheckButton", + icon: "back_hand", + width: 4, + }, + }, + defaultValue: { + background: true, + }, }, ], }, { - value: "quickcomposer.windows.message.sendCommand", - label: "窗口命令", - icon: "window", + value: "quickcomposer.windows.automation.sendKeys", + label: "发送按键", + icon: "keyboard_alt", config: [ { - key: "type", - defaultValue: "custom", - hidden: true, - }, - { - key: "message", - label: "命令", - component: "q-select", - icon: "code", + key: "keys", + label: "按键序列", + component: "VariableInput", + icon: "keyboard", width: 12, - options: [ - { label: "关闭窗口", value: "0x0010" }, // WM_CLOSE - { label: "销毁窗口", value: "0x0002" }, // WM_DESTROY - { label: "激活窗口", value: "0x0006" }, // WM_ACTIVATE - { label: "显示窗口", value: "0x0018" }, // WM_SHOWWINDOW - { label: "重绘窗口", value: "0x000F" }, // WM_PAINT - { label: "窗口尺寸", value: "0x0005" }, // WM_SIZE - { label: "窗口位置", value: "0x0003" }, // WM_MOVE - ], - defaultValue: "0x0010", + placeholder: "按键组合,多个逗号隔开,如:ctrl+a,a,b", }, { - key: "wParam", - label: "参数1", - component: "NumberInput", - icon: "input", - width: 6, - defaultValue: 0, - }, - { - key: "lParam", - label: "参数2", - component: "NumberInput", - icon: "input", - width: 6, - defaultValue: 0, - }, - ], - }, - { - value: "quickcomposer.windows.message.sendCustom", - label: "自定义消息", - icon: "settings", - config: [ - { - key: "type", - defaultValue: "custom", - hidden: true, - }, - { - key: "message", - label: "消息ID", - component: "NumberInput", - icon: "tag", + key: "options", + component: "OptionEditor", width: 12, - defaultValue: 0, - }, - { - key: "wParam", - label: "wParam", - component: "NumberInput", - icon: "input", - width: 6, - defaultValue: 0, - }, - { - key: "lParam", - label: "lParam", - component: "NumberInput", - icon: "input", - width: 6, - defaultValue: 0, + options: { + control: { + label: "目标控件", + component: "VariableInput", + options: { + items: controlClass, + }, + icon: "class", + width: 8, + placeholder: "可选,目标控件的类名", + }, + background: { + label: "后台操作", + component: "CheckButton", + icon: "back_hand", + width: 4, + }, + }, + defaultValue: { + background: true, + }, }, ], },