diff --git a/plugin/lib/csharp/index.js b/plugin/lib/csharp/index.js index 338c820..147e361 100644 --- a/plugin/lib/csharp/index.js +++ b/plugin/lib/csharp/index.js @@ -173,15 +173,16 @@ const runCsharpFeature = async (feature, args = [], options = {}) => { if (killPrevious && currentChild) { quickcommand.kill(currentChild.pid, "SIGKILL"); } + console.log(featureExePath, args.join(" ")); currentChild = child_process.execFile( featureExePath, args, { encoding: null, }, - (err, stdout) => { - console.log(iconv.decode(stdout, "gbk")); - if (err) reject(iconv.decode(stdout, "gbk")); + (err, stdout, stderr) => { + if (err || Buffer.byteLength(stderr) > 0) + reject(iconv.decode(stderr || stdout, "gbk")); else reslove(iconv.decode(stdout, "gbk")); } ); diff --git a/plugin/lib/csharp/sendmessage.cs b/plugin/lib/csharp/sendmessage.cs index 38ce5d1..61fb173 100644 --- a/plugin/lib/csharp/sendmessage.cs +++ b/plugin/lib/csharp/sendmessage.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Drawing; using System.Collections.Generic; using System.Linq; +using System.Web.Script.Serialization; public class AutomationTool { @@ -127,6 +128,9 @@ public class AutomationTool [DllImport("user32.dll")] private static extern uint MapVirtualKey(uint uCode, uint uMapType); + + [DllImport("user32.dll")] + private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); #endregion public static void Main(string[] args) @@ -137,42 +141,61 @@ public class AutomationTool 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 { + List targetWindows = FindTargetWindows(args); + if (targetWindows.Count == 0) + { + throw new Exception("未找到目标窗口"); + } + + string type = GetArgumentValue(args, "-type"); + IntPtr targetHandle = targetWindows[0]; // 总是使用第一个窗口 + Dictionary operatedWindow = null; + switch (type.ToLower()) { - case "keyboard": - HandleKeyboardOperation(args); - break; - case "mouse": - HandleMouseOperation(args); - break; case "inspect": - HandleInspectOperation(args); + // inspect 操作只获取第一个匹配窗口的控件树 + string filter = GetArgumentValue(args, "-filter"); + bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false"); + + // 只在非后台操作时激活窗口 + if (!background) + { + SetForegroundWindow(targetHandle); + Thread.Sleep(50); + } + + string treeJson = GetControlsTree(targetHandle, filter); + if (!string.IsNullOrEmpty(treeJson)) + { + Console.WriteLine("[" + treeJson + "]"); + } + return; // 直接返回,不输出窗口信息 + + case "keyboard": + operatedWindow = HandleKeyboardOperation(targetHandle, args); break; + + case "mouse": + operatedWindow = HandleMouseOperation(targetHandle, args); + break; + default: - Console.WriteLine("Error: 不支持的操作类型"); - break; + throw new Exception("不支持的操作类型"); + } + + // 输出操作的窗口信息 + if (operatedWindow != null) + { + var serializer = new JavaScriptSerializer(); + Console.WriteLine(serializer.Serialize(operatedWindow)); } } catch (Exception ex) { - Console.WriteLine(string.Format("Error: {0}", ex.Message)); + Console.Error.WriteLine(string.Format("Error: {0}", ex.Message)); } } @@ -182,133 +205,145 @@ public class AutomationTool string method = GetArgumentValue(args, "-method") ?? "title"; string value = GetArgumentValue(args, "-window") ?? ""; - if (method == "active") + // 如果是active方法,直接返回当前活动窗口 + if (method.ToLower() == "active") { targetWindows.Add(GetForegroundWindow()); return targetWindows; } - if (method == "handle") + // 如果是handle方法,直接返回指定句柄 + if (method.ToLower() == "handle") { targetWindows.Add(new IntPtr(long.Parse(value))); return targetWindows; } - // title方式 + // 如果没有指定窗口值,返回空列表 if (string.IsNullOrEmpty(value)) { return targetWindows; } - // 查找所有匹配的窗口 - EnumWindows((hwnd, param) => + switch (method.ToLower()) { - StringBuilder title = new StringBuilder(256); - GetWindowText(hwnd, title, title.Capacity); - string windowTitle = title.ToString(); + case "process": + // 通过进程名查找 + var processes = System.Diagnostics.Process.GetProcessesByName(value); + foreach (var process in processes) + { + if (process.MainWindowHandle != IntPtr.Zero) + { + targetWindows.Add(process.MainWindowHandle); + } + } + break; - if (!string.IsNullOrEmpty(windowTitle) && - windowTitle.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) - { - targetWindows.Add(hwnd); - } - return true; - }, IntPtr.Zero); + case "class": + // 通过窗口类名查找 + EnumWindows((hwnd, param) => + { + if (!IsWindowVisible(hwnd)) + { + return true; + } + + StringBuilder className = new StringBuilder(256); + GetClassName(hwnd, className, className.Capacity); + string windowClassName = className.ToString(); + + if (windowClassName.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) + { + targetWindows.Add(hwnd); + } + return true; + }, IntPtr.Zero); + break; + + case "title": + default: + // 通过窗口标题查找(支持模糊匹配) + EnumWindows((hwnd, param) => + { + StringBuilder title = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + string windowTitle = title.ToString(); + + if (!string.IsNullOrEmpty(windowTitle) && + windowTitle.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) + { + targetWindows.Add(hwnd); + } + return true; + }, IntPtr.Zero); + break; + } 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); - } + Console.WriteLine(string.Format("Error: 未找到匹配的窗口 (method={0}, value={1})", method, value)); } return targetWindows; } - private static void HandleKeyboardOperation(string[] args) + private static Dictionary HandleKeyboardOperation(IntPtr targetHandle, string[] args) { 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)) + // 如果指定了控件,递归查找控件句柄 + IntPtr controlHandle = IntPtr.Zero; + if (!string.IsNullOrEmpty(control)) { - Console.WriteLine("Error: keyboard操作需要指定 -action 参数"); - return; - } + StringBuilder windowTitle = new StringBuilder(256); + GetWindowText(targetHandle, windowTitle, windowTitle.Capacity); - var targetWindows = FindTargetWindows(args); - if (targetWindows.Count == 0) - { - return; - } - - foreach (IntPtr hWnd in targetWindows) - { - IntPtr targetHandle = hWnd; - - // 如果指定了控件,递归查找控件句柄 - if (!string.IsNullOrEmpty(control)) + controlHandle = FindControl(targetHandle, control); + if (controlHandle == IntPtr.Zero) { - IntPtr hControl = FindControl(hWnd, control); - if (hControl != IntPtr.Zero) + throw new Exception(string.Format("在窗口中未找到指定控件 (窗口句柄={0}, 标题=\"{1}\", 控件类名=\"{2}\")", + targetHandle.ToInt64(), windowTitle.ToString(), control)); + } + targetHandle = controlHandle; + } + + // 只在非后台操作时激活窗口 + if (!background) + { + SetForegroundWindow(targetHandle); + Thread.Sleep(50); + } + + switch (action.ToLower()) + { + case "keys": + if (string.IsNullOrEmpty(value)) { - targetHandle = hControl; + throw new Exception("发送按键需要指定 -value 参数"); } - else + SendKeys(targetHandle, value); + break; + + case "text": + if (string.IsNullOrEmpty(value)) { - Console.WriteLine(string.Format("Warning: 在窗口 0x{0:X} 中未找到指定控件", hWnd.ToInt64())); - continue; + throw new Exception("发送文本需要指定 -value 参数"); } - } + SendText(targetHandle, value); + break; - // 只在非后台操作时激活窗口 - 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; - } + default: + throw new Exception("不支持的keyboard操作类型"); } + + // 返回操作结果 + return GetBasicWindowInfo(targetHandle, controlHandle); } - private static void HandleMouseOperation(string[] args) + private static Dictionary HandleMouseOperation(IntPtr targetHandle, string[] args) { string control = GetArgumentValue(args, "-control"); string controlText = GetArgumentValue(args, "-text"); @@ -318,87 +353,83 @@ public class AutomationTool if (string.IsNullOrEmpty(action)) { - Console.WriteLine("Error: mouse操作需要指定 -action 参数"); - return; + throw new Exception("mouse操作需要指定 -action 参数"); } - var targetWindows = FindTargetWindows(args); - if (targetWindows.Count == 0) + // 如果指定了控件类名和文本,查找匹配的控件 + IntPtr controlHandle = IntPtr.Zero; + if (!string.IsNullOrEmpty(control) || !string.IsNullOrEmpty(controlText)) { - return; + StringBuilder windowTitle = new StringBuilder(256); + GetWindowText(targetHandle, windowTitle, windowTitle.Capacity); + + controlHandle = FindControlByTextAndClass(targetHandle, controlText, control); + if (controlHandle == IntPtr.Zero) + { + throw new Exception(string.Format("在窗口中未找到指定控件 (窗口句柄={0}, 标题=\"{1}\", 控件类名=\"{2}\", 控件文本=\"{3}\")", + targetHandle.ToInt64(), windowTitle.ToString(), control ?? "", controlText ?? "")); + } + targetHandle = controlHandle; } - foreach (IntPtr hWnd in targetWindows) + // 只在非后台操作时激活窗口 + if (!background) { - IntPtr targetHandle = hWnd; + SetForegroundWindow(targetHandle); + Thread.Sleep(50); + } - // 如果指定了控件类名和文本,查找匹配的控件 - if (!string.IsNullOrEmpty(control) || !string.IsNullOrEmpty(controlText)) + // 获取点击坐标 + int x = 0, y = 0; + if (!string.IsNullOrEmpty(position)) + { + // 使用指定坐标 + string[] pos = position.Split(','); + if (pos.Length == 2) { - 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; + 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; + } + + // 返回操作结果 + return GetBasicWindowInfo(targetHandle, controlHandle); } private static void SendKeys(IntPtr hWnd, string keys) @@ -735,10 +766,8 @@ public class AutomationTool return; } - // 输出所有匹配窗口的信息 - StringBuilder json = new StringBuilder(); - json.Append("["); - bool firstWindow = true; + // 创建一个列表存储所有窗口的控件树数组 + List allWindowTrees = new List(); foreach (IntPtr hWnd in targetWindows) { @@ -749,17 +778,18 @@ public class AutomationTool Thread.Sleep(50); } - if (!firstWindow) + // 获取当前窗口的控件树并添加到列表 + // 注意:每个控件树已经是一个数组 + string treeJson = GetControlsTree(hWnd, filter); + if (!string.IsNullOrEmpty(treeJson) && treeJson != "{}") { - json.Append(","); + // 将每个控件树作为一个数组元素添加 + allWindowTrees.Add("[" + treeJson + "]"); } - firstWindow = false; - - json.Append(GetControlsTree(hWnd, filter)); } - json.Append("]"); - Console.WriteLine(json.ToString()); + // 将所有窗口的控件树数组合并成一个大数组 + Console.WriteLine("[" + string.Join(",", allWindowTrees) + "]"); } private static string GetControlsTree(IntPtr parentHandle, string filter, int depth = 0) @@ -791,10 +821,10 @@ public class AutomationTool 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(), + "\"handle\":\"{0}\",\"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(), @@ -846,10 +876,15 @@ sendmessage.exe -type <操作类型> [参数...] 通用参数: -------- -+-method 窗口查找方式(可选,默认title) -+ 可选值:title(标题), handle(句柄), active(活动窗口) -+ --window 窗口标题或句柄(支持模糊匹配) +-method 窗口查找方式(可选,默认title) + 可选值: + - title 窗口标题(支持模糊匹配) + - handle 窗口句柄 + - active 当前活动窗口 + - process 进程名 + - class 窗口类名(支持模糊匹配) + +-window 要查找的窗口值(根据method解释) -control 控件类名 -background 后台操作,不激活窗口,默认激活 @@ -891,15 +926,77 @@ sendmessage.exe -type <操作类型> [参数...] 7. 操作当前活动窗口: sendmessage.exe -type keyboard -method active -value ""ctrl+s"" +8. 通过进程名查找窗口: + sendmessage.exe -type keyboard -method process -window ""notepad"" -value ""Hello"" + +9. 通过窗口类名查找: + sendmessage.exe -type keyboard -method class -window ""Chrome"" -value ""Hello"" # 会匹配 Chrome_WidgetWin_1 + sendmessage.exe -type keyboard -method class -window ""Chrome_WidgetWin_1"" -value ""Hello"" # 精确匹配 + +返回值: +------ +1. 均为JSON格式 +2. inspect操作返回控件树信息 +3. 其他操作返回操作的控件信息及其所在窗口信息 +4. 失败均抛出异常 + 注意事项: -------- -1. 窗口标题支持模糊匹配 -2. 控件类名需要完全匹配 -3. 后台操作可能会影响某些程序的响应 -4. 建议先用inspect获取正确的控件信息再进行操作 -5. handle方式查找窗口时需要提供正确的窗口句柄 -6. active方式不需要提供window参数,直接操作当前活动窗口 +1. 窗口标题、类名支持模糊匹配,active方式可不提供window参数 +2. 所有操作都只会处理第一个匹配的窗口 "; Console.WriteLine(help); } + + private static Dictionary GetBasicWindowInfo(IntPtr hwnd, IntPtr controlHandle = default(IntPtr)) + { + var info = new Dictionary(); + + // 获取窗口信息 + StringBuilder title = new StringBuilder(256); + StringBuilder className = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + GetClassName(hwnd, className, className.Capacity); + + int processId = 0; + GetWindowThreadProcessId(hwnd, out processId); + string processName = ""; + try + { + var process = System.Diagnostics.Process.GetProcessById(processId); + processName = process.ProcessName; + } + catch { } + + // 窗口信息放在 window 键下 + info["window"] = new Dictionary + { + { "handle", hwnd.ToInt64() }, + { "title", title.ToString() }, + { "class", className.ToString() }, + { "process", processName } + }; + + // 如果有控件信息,添加到顶层 + if (controlHandle != IntPtr.Zero) + { + StringBuilder controlTitle = new StringBuilder(256); + StringBuilder controlClassName = new StringBuilder(256); + GetWindowText(controlHandle, controlTitle, controlTitle.Capacity); + GetClassName(controlHandle, controlClassName, controlClassName.Capacity); + + info["handle"] = controlHandle.ToInt64(); + info["title"] = controlTitle.ToString(); + info["class"] = controlClassName.ToString(); + } + else + { + // 如果没有控件,设置为 null + info["handle"] = null; + info["title"] = null; + info["class"] = null; + } + + return info; + } } diff --git a/plugin/lib/csharp/window.cs b/plugin/lib/csharp/window.cs index 0c3a9b0..c940257 100644 --- a/plugin/lib/csharp/window.cs +++ b/plugin/lib/csharp/window.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Diagnostics; using System.Web.Script.Serialization; +using System.Collections.Generic; public class WindowManager { @@ -110,33 +111,47 @@ public class WindowManager return; } - string type = GetArgumentValue(args, "-type"); - if (string.IsNullOrEmpty(type)) - { - Console.Error.WriteLine("Error: 必须指定操作类型 (-type)"); - return; - } - try { - IntPtr hwnd = GetWindowHandle(args); - if (hwnd == IntPtr.Zero) + List targetWindows = GetTargetWindows(args); + if (targetWindows.Count == 0) { - Console.Error.WriteLine("Error: 未找到目标窗口"); + throw new Exception("未找到目标窗口"); + } + + string type = GetArgumentValue(args, "-type"); + if (type.ToLower() == "info") + { + var allWindowInfo = new List>(); + foreach (IntPtr windowHandle in targetWindows) + { + var windowInfo = GetBasicWindowInfo(windowHandle); + if (windowInfo != null) + { + allWindowInfo.Add(windowInfo); + } + } + var serializer = new JavaScriptSerializer(); + Console.WriteLine(serializer.Serialize(allWindowInfo)); return; } + IntPtr targetHandle = targetWindows[0]; + Dictionary operatedWindow = null; + switch (type.ToLower()) { case "topmost": bool isTopMost = bool.Parse(GetArgumentValue(args, "-value") ?? "true"); - SetWindowPos(hwnd, isTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + SetWindowPos(targetHandle, isTopMost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "opacity": int opacity = int.Parse(GetArgumentValue(args, "-value") ?? "100"); - SetWindowLong(hwnd, GWL_EXSTYLE, GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); - SetLayeredWindowAttributes(hwnd, 0, (byte)(opacity * 2.55), LWA_ALPHA); + SetWindowLong(targetHandle, GWL_EXSTYLE, GetWindowLong(targetHandle, GWL_EXSTYLE) | WS_EX_LAYERED); + SetLayeredWindowAttributes(targetHandle, 0, (byte)(opacity * 2.55), LWA_ALPHA); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "rect": @@ -147,56 +162,69 @@ public class WindowManager int y = int.Parse(rectValues[1]); int width = int.Parse(rectValues[2]); int height = int.Parse(rectValues[3]); - MoveWindow(hwnd, x, y, width, height, true); + MoveWindow(targetHandle, x, y, width, height, true); } + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "state": string state = GetArgumentValue(args, "-value") ?? "normal"; int cmdShow = state == "maximize" ? SW_MAXIMIZE : state == "minimize" ? SW_MINIMIZE : SW_NORMAL; - ShowWindow(hwnd, cmdShow); + ShowWindow(targetHandle, cmdShow); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "visible": bool visible = bool.Parse(GetArgumentValue(args, "-value") ?? "true"); - ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); + ShowWindow(targetHandle, visible ? SW_SHOW : SW_HIDE); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "close": - PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + PostMessage(targetHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "focus": - if (IsIconic(hwnd)) + if (IsIconic(targetHandle)) { - ShowWindow(hwnd, SW_RESTORE); + ShowWindow(targetHandle, SW_RESTORE); } - SetForegroundWindow(hwnd); + SetForegroundWindow(targetHandle); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "border": bool hasBorder = bool.Parse(GetArgumentValue(args, "-value") ?? "true"); - int style = GetWindowLong(hwnd, GWL_STYLE); + int style = GetWindowLong(targetHandle, GWL_STYLE); style = hasBorder ? style | WS_CAPTION : style & ~WS_CAPTION; - SetWindowLong(hwnd, GWL_STYLE, style); + SetWindowLong(targetHandle, GWL_STYLE, style); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "clickthrough": bool isTransparent = bool.Parse(GetArgumentValue(args, "-value") ?? "true"); - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); + int exStyle = GetWindowLong(targetHandle, GWL_EXSTYLE); exStyle |= WS_EX_LAYERED; exStyle = isTransparent ? exStyle | WS_EX_TRANSPARENT : exStyle & ~WS_EX_TRANSPARENT; - SetWindowLong(hwnd, GWL_EXSTYLE, exStyle); + SetWindowLong(targetHandle, GWL_EXSTYLE, exStyle); + operatedWindow = GetBasicWindowInfo(targetHandle); break; case "info": - GetWindowInfo(hwnd); + operatedWindow = GetBasicWindowInfo(targetHandle); break; default: Console.Error.WriteLine("Error: 不支持的操作类型"); - break; + return; + } + + if (operatedWindow != null) + { + var serializer = new JavaScriptSerializer(); + Console.WriteLine(serializer.Serialize(operatedWindow)); } } catch (Exception ex) @@ -205,148 +233,106 @@ public class WindowManager } } - private static IntPtr GetWindowHandle(string[] args) + private static List GetTargetWindows(string[] args) { + List targetWindows = new List(); string method = GetArgumentValue(args, "-method") ?? "title"; string value = GetArgumentValue(args, "-window") ?? ""; switch (method.ToLower()) { case "handle": - return new IntPtr(long.Parse(value)); - case "active": - return GetForegroundWindow(); - default: // title - if (string.IsNullOrEmpty(value)) - { - return IntPtr.Zero; - } + targetWindows.Add(new IntPtr(long.Parse(value))); + break; - IntPtr foundWindow = IntPtr.Zero; + case "active": + targetWindows.Add(GetForegroundWindow()); + break; + + case "process": + var processes = Process.GetProcessesByName(value); + foreach (var process in processes) + { + if (process.MainWindowHandle != IntPtr.Zero) + { + targetWindows.Add(process.MainWindowHandle); + } + } + break; + + case "class": EnumWindows((hwnd, param) => { - StringBuilder title = new StringBuilder(256); - GetWindowText(hwnd, title, title.Capacity); - string windowTitle = title.ToString(); - - if (!string.IsNullOrEmpty(windowTitle) && - windowTitle.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) + if (!IsWindowVisible(hwnd)) { - foundWindow = hwnd; - return false; // 找到后停止枚举 + return true; + } + + StringBuilder className = new StringBuilder(256); + GetClassName(hwnd, className, className.Capacity); + string windowClassName = className.ToString(); + + if (windowClassName.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) + { + targetWindows.Add(hwnd); } return true; }, IntPtr.Zero); + break; - return foundWindow; + case "title": + default: + if (!string.IsNullOrEmpty(value)) + { + EnumWindows((hwnd, param) => + { + StringBuilder title = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + string windowTitle = title.ToString(); + + if (!string.IsNullOrEmpty(windowTitle) && + windowTitle.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) + { + targetWindows.Add(hwnd); + } + return true; + }, IntPtr.Zero); + } + break; } + + if (targetWindows.Count == 0) + { + throw new Exception("未找到匹配的窗口"); + } + + return targetWindows; } - private static void GetWindowInfo(IntPtr hwnd) + private static Dictionary GetBasicWindowInfo(IntPtr hwnd) { - var windowRect = new RECT(); - GetWindowRect(hwnd, out windowRect); - - var clientRect = new RECT(); - GetClientRect(hwnd, out clientRect); - - var titleText = new StringBuilder(256); - GetWindowText(hwnd, titleText, 256); - - var className = new StringBuilder(256); - GetClassName(hwnd, className, 256); + StringBuilder title = new StringBuilder(256); + StringBuilder className = new StringBuilder(256); + GetWindowText(hwnd, title, title.Capacity); + GetClassName(hwnd, className, className.Capacity); uint processId = 0; GetWindowThreadProcessId(hwnd, out processId); - string processName = ""; - string processPath = ""; try { - Process process = Process.GetProcessById((int)processId); + var process = Process.GetProcessById((int)processId); processName = process.ProcessName; - - var startInfo = new ProcessStartInfo - { - FileName = "wmic", - Arguments = string.Format("process where ProcessId={0} get ExecutablePath /value", processId), - UseShellExecute = false, - RedirectStandardOutput = true, - CreateNoWindow = true - }; - using (var proc = Process.Start(startInfo)) - { - string output = proc.StandardOutput.ReadToEnd(); - if (!string.IsNullOrEmpty(output)) - { - string[] lines = output.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - foreach (string line in lines) - { - if (line.StartsWith("ExecutablePath=")) - { - processPath = line.Substring("ExecutablePath=".Length); - break; - } - } - } - } } catch { } - int style = GetWindowLong(hwnd, GWL_STYLE); - int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE); - - var data = new + return new Dictionary { - handle = hwnd.ToInt64(), - processId = processId, - process = new - { - name = processName, - path = processPath - }, - title = titleText.ToString(), - className = className.ToString(), - window = new - { - x = windowRect.Left, - y = windowRect.Top, - width = windowRect.Right - windowRect.Left, - height = windowRect.Bottom - windowRect.Top - }, - client = new - { - width = clientRect.Right - clientRect.Left, - height = clientRect.Bottom - clientRect.Top - }, - state = new - { - visible = IsWindowVisible(hwnd), - minimized = IsIconic(hwnd), - maximized = IsZoomed(hwnd), - focused = GetForegroundWindow() == hwnd - }, - style = new - { - border = (style & WS_BORDER) != 0, - caption = (style & WS_CAPTION) != 0, - child = (style & WS_CHILD) != 0, - popup = (style & WS_POPUP) != 0, - sysmenu = (style & WS_SYSMENU) != 0, - minimizeBox = (style & WS_MINIMIZEBOX) != 0, - maximizeBox = (style & WS_MAXIMIZEBOX) != 0 - }, - exStyle = new - { - topmost = (exStyle & WS_EX_TOPMOST) != 0, - transparent = (exStyle & WS_EX_TRANSPARENT) != 0, - toolWindow = (exStyle & WS_EX_TOOLWINDOW) != 0, - layered = (exStyle & WS_EX_LAYERED) != 0 - } + { "handle", hwnd.ToInt64() }, + { "title", title.ToString() }, + { "class", className.ToString() }, + { "process", processName } }; - - var serializer = new JavaScriptSerializer(); - Console.WriteLine(serializer.Serialize(data)); } private static string GetArgumentValue(string[] args, string key) @@ -384,12 +370,14 @@ window.exe -type <操作类型> [参数...] 通用参数: -------- -method 窗口查找方式(可选,默认title) - 可选值:title, handle, active + 可选值: + - title 窗口标题(支持模糊匹配) + - handle 窗口句柄 + - active 当前活动窗口 + - process 进程名 + - class 窗口类名(支持模糊匹配) --window 窗口标题或句柄 - 示例:-window ""记事本"" - --value 操作值,根据不同操作类型有不同含义 +-window 要查找的窗口值(根据method解释) 操作参数说明: ----------- @@ -437,17 +425,23 @@ window.exe -type <操作类型> [参数...] 7. 获取窗口信息: window.exe -type info -window ""记事本"" +8. 通过进程名查找窗口: + window.exe -type info -method process -window ""notepad"" + +9. 通过窗口类名查找: + window.exe -type info -method class -window ""Chrome"" # 会匹配 Chrome_WidgetWin_1 + 返回值: ------ -1. info操作返回JSON格式的窗口信息 -2. 其他操作无返回值,执行失败时输出错误信息 +1. 均为JSON格式 +2. info操作返回所有匹配窗口信息 +3. 其他操作返回操作的窗口信息 +4. 失败均抛出异常 注意事项: -------- -1. 窗口标题支持模糊匹配 -2. handle方式查找窗口时需要提供正确的窗口句柄 -3. active方式不需要提供window参数,直接操作当前活动窗口 -4. 某些操作可能需要管理员权限 +1. 窗口标题、类名支持模糊匹配,active方式可不提供window参数 +2. 只有info操作会返回所有匹配窗口的信息,其他操作只会操作第一个匹配的窗口 "; Console.WriteLine(help); } diff --git a/plugin/lib/quickcomposer/windows/automation.js b/plugin/lib/quickcomposer/windows/automation.js index 94008c9..521dc52 100644 --- a/plugin/lib/quickcomposer/windows/automation.js +++ b/plugin/lib/quickcomposer/windows/automation.js @@ -2,11 +2,10 @@ const { runCsharpFeature } = require("../../csharp"); /** * 列出所有元素 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 * @param {object} options 选项 * @param {string} options.filter 过滤条件 - * @param {boolean} options.background 是否后台操作 * @returns {object[]} 元素列表 */ const listElements = async function (method, window, options = {}) { @@ -17,28 +16,29 @@ const listElements = async function (method, window, options = {}) { 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; + const result = await runCsharpFeature("automation", args); + console.log(result); + if (result) return JSON.parse(result); + } catch (err) { + console.log(err); } + return []; }; /** * 点击元素 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 - * @param {string} by 查找方式:name/class/type/automationid + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 + * @param {string} by 查找方式:"name"|"class"|"type"|"automationid" * @param {string} value 查找值 * @param {object} options 选项 - * @param {string} options.pattern 点击模式:invoke/toggle + * @param {string} options.pattern 点击模式:"invoke"|"toggle" * @param {boolean} options.background 是否后台操作 - * @returns {boolean} 是否成功 + * @returns {Object} 操作结果 + * @property {boolean} success 是否成功 + * @property {Object} element 操作的元素信息 + * @property {Object} element.window 操作的元素所在的窗口信息 */ const clickElement = async function (method, window, by, value, options = {}) { const { pattern = "invoke", background = false } = options; @@ -48,24 +48,31 @@ const clickElement = async function (method, window, by, value, options = {}) { 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)); + let error; + try { + const result = await runCsharpFeature("automation", args); + if (result) { + return { success: true, element: JSON.parse(result) }; + } + } catch (err) { + error = err.toString(); } - return true; + return { success: true, error }; }; /** * 设置元素值 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 - * @param {string} by 查找方式:name/class/type/automationid + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"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} 是否成功 + * @returns {Object} 操作结果 + * @property {boolean} success 是否成功 + * @property {Object} element 操作的元素信息 + * @property {Object} element.window 操作的元素所在的窗口信息 */ const setElementValue = async function ( method, @@ -90,19 +97,21 @@ const setElementValue = async function ( 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)); + let error; + try { + const result = await runCsharpFeature("automation", args); + if (result) return { success: true, element: JSON.parse(result) }; + } catch (err) { + error = err.toString(); } - return true; + return { success: false, error }; }; /** * 获取元素值 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 - * @param {string} by 查找方式:name/class/type/automationid + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 + * @param {string} by 查找方式:"name"|"class"|"type"|"automationid" * @param {string} value 查找值 * @param {object} options 选项 * @param {boolean} options.background 是否后台操作 @@ -122,38 +131,32 @@ const getElementValue = async function ( 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)); - } + let error; try { - return JSON.parse(result); - } catch (error) { - console.error("解析元素值失败:", error); - return null; + const result = await runCsharpFeature("automation", args); + if (result) return JSON.parse(result); + } catch (err) { + error = err.toString(); } + return { success: false, error }; }; /** * 检查元素 * @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; + const result = await runCsharpFeature("automation", args); + if (result) return JSON.parse(result); + } catch (err) { + console.log(err); } + return []; }; module.exports = { diff --git a/plugin/lib/quickcomposer/windows/sendmessage.js b/plugin/lib/quickcomposer/windows/sendmessage.js index f9d2793..0b9bb44 100644 --- a/plugin/lib/quickcomposer/windows/sendmessage.js +++ b/plugin/lib/quickcomposer/windows/sendmessage.js @@ -2,13 +2,16 @@ const { runCsharpFeature } = require("../../csharp"); /** * 键盘操作 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 * @param {string} keys 键盘按键 * @param {object} options 选项 * @param {string} options.control 控件类名 * @param {boolean} options.background 是否后台操作 - * @returns {boolean} 是否成功 + * @returns {Object} 操作结果 + * @property {boolean} success 是否成功 + * @property {Object} control 控件信息 + * @property {string} control.window 控件所在窗口的信息 */ const sendKeys = async function (method, window, keys, options = {}) { const { control, background = false } = options; @@ -17,23 +20,33 @@ const sendKeys = async function (method, window, keys, options = {}) { if (method !== "active" && window) args.push("-window", window); if (control) args.push("-control", control); args.push("-background", background.toString()); - - const result = await runCsharpFeature("sendmessage", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); + let error; + try { + const result = await runCsharpFeature("sendmessage", args); + if (result) { + return { + success: true, + control: JSON.parse(result), + }; + } + } catch (err) { + error = err.toString(); } - return true; + return { success: false, error }; }; /** * 发送文本 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 * @param {string} text 文本 * @param {object} options 选项 * @param {string} options.control 控件类名 * @param {boolean} options.background 是否后台操作 - * @returns {boolean} 是否成功 + * @returns {Object} 操作结果 + * @property {boolean} success 是否成功 + * @property {Object} control 控件信息 + * @property {string} control.window 控件所在窗口的信息 */ const sendText = async function (method, window, text, options = {}) { const { control, background = false } = options; @@ -43,32 +56,34 @@ const sendText = async function (method, window, text, options = {}) { if (method !== "active" && window) args.push("-window", window); if (control) args.push("-control", control); args.push("-background", background.toString()); - - const result = await runCsharpFeature("sendmessage", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); + let error; + try { + const result = await runCsharpFeature("sendmessage", args); + if (result) { + return { success: true, control: JSON.parse(result) }; + } + } catch (err) { + error = err.toString(); } - return true; + return { success: false, error }; }; /** * 鼠标点击 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 - * @param {string} action 动作:click/doubleClick/rightClick + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"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} 是否成功 + * @returns {Object} 操作结果 + * @property {boolean} success 是否成功 + * @property {Object} control 控件信息 + * @property {string} control.window 控件所在窗口的信息 */ -const click = async function ( - method, - window, - action = "click", - options = {} -) { +const click = async function (method, window, action = "click", options = {}) { const { control, text, pos, background = false } = options; const args = ["-type", "mouse", "-action", action]; @@ -79,17 +94,22 @@ const click = async function ( if (pos) args.push("-pos", pos); args.push("-background", background.toString()); - const result = await runCsharpFeature("sendmessage", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); + let error; + try { + const result = await runCsharpFeature("sendmessage", args); + if (result) { + return { success: true, control: JSON.parse(result) }; + } + } catch (err) { + error = err.toString(); } - return true; + return { success: false, error }; }; /** * 获取窗口控件树 - * @param {string} method 窗口类型:title/handle/active - * @param {string} window 窗口标题/句柄 + * @param {string} method 窗口类型:"title"|"handle"|"process"|"class"|"active" + * @param {string} window 窗口标题、句柄、进程名、类名 * @param {object} options 选项 * @param {string} options.filter 过滤条件 * @param {boolean} options.background 是否后台操作 @@ -103,17 +123,13 @@ const inspectWindow = async function (method, window, options = {}) { if (method !== "active" && window) args.push("-window", window); if (filter) args.push("-filter", filter); args.push("-background", background.toString()); - - const result = await runCsharpFeature("sendmessage", args); - if (result && result.startsWith("Error:")) { - throw new Error(result.substring(7)); - } try { - return JSON.parse(result); + const result = await runCsharpFeature("sendmessage", args); + if (result) return JSON.parse(result); } catch (error) { - console.error("解析控件树失败:", error); - return null; + console.log(error); } + return []; }; module.exports = { diff --git a/plugin/lib/quickcomposer/windows/window.js b/plugin/lib/quickcomposer/windows/window.js index 9c62a45..22b0f99 100644 --- a/plugin/lib/quickcomposer/windows/window.js +++ b/plugin/lib/quickcomposer/windows/window.js @@ -1,10 +1,12 @@ const { runCsharpFeature } = require("../../csharp"); /** - * 窗口置顶 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) - * @param {boolean} isTopMost 是否置顶 + * 窗口置顶,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被置顶的窗口信息 */ async function setTopMost(method, value, isTopMost) { const args = [ @@ -17,14 +19,29 @@ async function setTopMost(method, value, isTopMost) { "-value", isTopMost.toString(), ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { + success: true, + window: JSON.parse(windowInfo), + }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口透明度 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 设置窗口透明度,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 * @param {number} opacity 透明度 0-100 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置透明度的窗口信息 */ async function setOpacity(method, value, opacity) { const args = [ @@ -37,17 +54,29 @@ async function setOpacity(method, value, opacity) { "-value", opacity.toString(), ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口位置和大小 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 设置窗口位置和大小,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 * @param {number} x X坐标 * @param {number} y Y坐标 * @param {number} width 宽度 * @param {number} height 高度 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置位置和大小的窗口信息 */ async function setWindowRect(method, value, x, y, width, height) { const args = [ @@ -60,14 +89,26 @@ async function setWindowRect(method, value, x, y, width, height) { "-value", `${x},${y},${width},${height}`, ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口状态 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) - * @param {string} state 状态:normal/maximize/minimize + * 设置窗口状态,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 + * @param {string} state 状态:"normal"|"maximize"|"minimize" + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置状态的窗口信息 */ async function setWindowState(method, value, state) { const args = [ @@ -80,34 +121,70 @@ async function setWindowState(method, value, state) { "-value", state, ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 关闭窗口 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 关闭窗口,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被关闭的窗口信息 */ async function closeWindow(method, value) { const args = ["-type", "close", "-method", method, "-window", value]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口焦点 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 设置窗口焦点,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置焦点的窗口信息 */ async function setFocus(method, value) { const args = ["-type", "focus", "-method", method, "-window", value]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口边框 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 设置窗口边框,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 * @param {boolean} hasBorder 是否显示边框 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置边框的窗口信息 */ async function setBorder(method, value, hasBorder) { const args = [ @@ -120,14 +197,26 @@ async function setBorder(method, value, hasBorder) { "-value", hasBorder.toString(), ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 设置窗口点击穿透 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) + * 设置窗口点击穿透,只操作第一个找到的窗口 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 * @param {boolean} isTransparent 是否点击穿透 + * @param {Object} result 结果 + * @param {boolean} result.success 是否成功 + * @param {Object} result.window 被设置点击穿透的窗口信息 */ async function setClickThrough(method, value, isTransparent) { const args = [ @@ -140,43 +229,33 @@ async function setClickThrough(method, value, isTransparent) { "-value", isTransparent.toString(), ]; - await runCsharpFeature("window", args); + let error; + try { + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) { + return { success: true, window: JSON.parse(windowInfo) }; + } + } catch (err) { + error = err.toString(); + } + return { success: false, error }; } /** - * 获取窗口信息 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) - * @returns {Object} 窗口信息 + * 获取窗口信息,返回所有匹配的窗口信息 + * @param {string} method 查找方式:"title"|"handle"|"process"|"class"|"active" + * @param {string} value 窗口标题、句柄、进程名、类名 + * @returns {Object} 所有匹配的窗口信息 */ async function getWindowInfo(method, value) { const args = ["-type", "info", "-method", method, "-window", value]; - const result = await runCsharpFeature("window", args); try { - return JSON.parse(result); - } catch (error) { - return {}; + const windowInfo = await runCsharpFeature("window", args); + if (windowInfo) return JSON.parse(windowInfo); + } catch (err) { + console.log(err); } -} - -/** - * 设置窗口可见性 - * @param {string} method 查找方式:title/handle/active - * @param {string} value 查找值(handle时为数字) - * @param {boolean} visible 是否可见 - */ -async function setVisible(method, value, visible) { - const args = [ - "-type", - "visible", - "-method", - method, - "-window", - value, - "-value", - visible.toString(), - ]; - await runCsharpFeature("window", args); + return []; } module.exports = { @@ -184,7 +263,6 @@ module.exports = { setOpacity, setWindowRect, setWindowState, - setVisible, closeWindow, setFocus, setBorder, diff --git a/src/components/popup/ResultMenu.vue b/src/components/popup/ResultMenu.vue index 2439dcf..a1442c6 100644 --- a/src/components/popup/ResultMenu.vue +++ b/src/components/popup/ResultMenu.vue @@ -193,6 +193,31 @@ export default { }, 0); }; + const formatValue = (value) => { + const liteView = (obj) => { + if (Array.isArray(obj)) { + return `[${obj.slice(0, 3).join(",")} ${ + obj.length > 3 ? "..." : "" + }]`; + } + const keys = Object.keys(obj); + return `{${keys.slice(0, 3).join(",")} ${ + keys.length > 3 ? "..." : "" + }}`; + }; + + if (value == null) return ""; + + switch (typeof value) { + case "string": + return value.replace(/\n/g, "\\n"); + case "object": + return liteView(value); + default: + return String(value); + } + }; + // 计算每列的最大宽度 const columnWidths = headers.map((header) => { const maxDataWidth = Math.max( @@ -201,7 +226,7 @@ export default { const value = obj[header]; return value === undefined || value === null ? 0 - : getDisplayWidth(String(value).replace(/\n/g, "\\n")); + : getDisplayWidth(formatValue(value)); }) ); return maxDataWidth; @@ -236,7 +261,7 @@ export default { const value = obj[header]; return value === undefined || value === null ? "" - : String(value).replace(/\n/g, "\\n"); + : formatValue(value); }) ) ), diff --git a/src/js/composer/commands/windowsCommands.js b/src/js/composer/commands/windowsCommands.js index dcac547..f898aef 100644 --- a/src/js/composer/commands/windowsCommands.js +++ b/src/js/composer/commands/windowsCommands.js @@ -107,26 +107,26 @@ const registryPaths = [ const searchWindowConfig = [ { key: "method", - label: "查找方式", + label: "窗口查找方式", component: "q-select", icon: "search", width: 3, options: [ { label: "标题", value: "title" }, - // { label: "类名", value: "class" }, + { label: "类名", value: "class" }, { label: "句柄", value: "handle" }, - // { label: "进程", value: "process" }, + { label: "进程名", value: "process" }, { label: "活动窗口", value: "active" }, ], defaultValue: "title", }, { key: "value", - label: "窗口标题/句柄", + label: "窗口标题/类名/句柄/进程名/活动窗口", component: "VariableInput", icon: "title", width: 9, - placeholder: "标题支持模糊匹配,选择活动窗口无需输入", + placeholder: "标题、类名支持模糊匹配,选择活动窗口无需输入", }, ]; @@ -244,24 +244,6 @@ export const windowsCommands = { }, ], }, - { - value: "quickcomposer.windows.window.setVisible", - label: "窗口可见性", - icon: "visibility", - config: [ - { - key: "visible", - component: "ButtonGroup", - icon: "visibility", - width: 12, - options: [ - { label: "显示", value: true }, - { label: "隐藏", value: false }, - ], - defaultValue: true, - }, - ], - }, { value: "quickcomposer.windows.window.closeWindow", label: "关闭窗口", @@ -317,29 +299,7 @@ export const windowsCommands = { desc: "Windows界面自动化操作", icon: "smart_button", isAsync: true, - 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.sendmessage.inspectWindow", @@ -1158,10 +1118,10 @@ export const windowsCommands = { ...searchWindowConfig, { key: "by", - label: "查找方式", + label: "元素查找方式", component: "q-select", icon: "search", - width: 4, + width: 3, options: [ { label: "名称", value: "name" }, { label: "类名", value: "class" }, @@ -1175,7 +1135,7 @@ export const windowsCommands = { label: "查找值", component: "VariableInput", icon: "text_fields", - width: 8, + width: 9, placeholder: "要点击的元素值", }, {