发送消息、窗口管理统一通过句柄操作

This commit is contained in:
fofolee
2025-01-18 11:18:08 +08:00
parent 7a7cb8dd54
commit 0ee3647261
6 changed files with 1140 additions and 1183 deletions

View File

@@ -53,6 +53,9 @@ public class AutomationTool
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
@@ -72,11 +75,6 @@ public class AutomationTool
private const int WM_RBUTTONUP = 0x0205;
private const int WM_LBUTTONDBLCLK = 0x0203;
// 添加按键状态标志
private const int KEYEVENTF_KEYDOWN = 0x0;
private const int KEYEVENTF_EXTENDEDKEY = 0x1;
private const int KEYEVENTF_KEYUP = 0x2;
private delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
@@ -164,8 +162,8 @@ public class AutomationTool
switch (type.ToLower())
{
case "inspect":
// inspect 操作只获取第一个匹配窗口的控件树
case "list":
// list 操作只获取第一个匹配窗口的控件树
string filter = GetArgumentValue(args, "-filter");
bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false");
@@ -184,23 +182,18 @@ public class AutomationTool
return; // 直接返回,不输出窗口信息
case "keyboard":
operatedWindow = HandleKeyboardOperation(targetHandle, args);
HandleKeyboardOperation(targetHandle, args);
Console.WriteLine("true");
break;
case "mouse":
operatedWindow = HandleMouseOperation(targetHandle, args);
HandleMouseOperation(targetHandle, args);
Console.WriteLine("true");
break;
default:
throw new Exception("不支持的操作类型");
}
// 输出操作的窗口信息
if (operatedWindow != null)
{
var serializer = new JavaScriptSerializer();
Console.WriteLine(serializer.Serialize(operatedWindow));
}
}
catch (Exception ex)
{
@@ -224,7 +217,12 @@ public class AutomationTool
// 如果是handle方法直接返回指定句柄
if (method.ToLower() == "handle")
{
targetWindows.Add(new IntPtr(long.Parse(value)));
IntPtr handle = new IntPtr(long.Parse(value));
if (!IsWindow(handle))
{
throw new Exception("指定的句柄不是一个有效的窗口句柄");
}
targetWindows.Add(handle);
return targetWindows;
}
@@ -296,7 +294,7 @@ public class AutomationTool
return targetWindows;
}
private static Dictionary<string, object> HandleKeyboardOperation(IntPtr targetHandle, string[] args)
private static void HandleKeyboardOperation(IntPtr targetHandle, string[] args)
{
string control = GetArgumentValue(args, "-control");
string action = GetArgumentValue(args, "-action");
@@ -349,10 +347,10 @@ public class AutomationTool
}
// 返回操作结果
return GetBasicWindowInfo(targetHandle, controlHandle);
return;
}
private static Dictionary<string, object> HandleMouseOperation(IntPtr targetHandle, string[] args)
private static void HandleMouseOperation(IntPtr targetHandle, string[] args)
{
string control = GetArgumentValue(args, "-control");
string controlText = GetArgumentValue(args, "-text");
@@ -438,7 +436,7 @@ public class AutomationTool
}
// 返回操作结果
return GetBasicWindowInfo(targetHandle, controlHandle);
return;
}
private static void SendKeys(IntPtr hWnd, string keys)
@@ -749,7 +747,6 @@ public class AutomationTool
}
else if (matchedControls.Count > 1)
{
Console.WriteLine("Warning: 找到多个匹配控件:");
foreach (IntPtr handle in matchedControls)
{
StringBuilder text = new StringBuilder(256);
@@ -764,43 +761,6 @@ public class AutomationTool
return matchedControls[0];
}
private static void HandleInspectOperation(string[] args)
{
string filter = GetArgumentValue(args, "-filter");
bool background = bool.Parse(GetArgumentValue(args, "-background") ?? "false");
var targetWindows = FindTargetWindows(args);
if (targetWindows.Count == 0)
{
return;
}
// 创建一个列表存储所有窗口的控件树数组
List<string> allWindowTrees = new List<string>();
foreach (IntPtr hWnd in targetWindows)
{
// 只在非后台操作时激活窗口
if (!background)
{
SetForegroundWindow(hWnd);
Thread.Sleep(50);
}
// 获取当前窗口的控件树并添加到列表
// 注意:每个控件树已经是一个数组
string treeJson = GetControlsTree(hWnd, filter);
if (!string.IsNullOrEmpty(treeJson) && treeJson != "{}")
{
// 将每个控件树作为一个数组元素添加
allWindowTrees.Add("[" + treeJson + "]");
}
}
// 将所有窗口的控件树数组合并成一个大数组
Console.WriteLine("[" + string.Join(",", allWindowTrees) + "]");
}
private static string GetControlsTree(IntPtr parentHandle, string filter, int depth = 0)
{
if (parentHandle == IntPtr.Zero)
@@ -881,7 +841,7 @@ sendmessage.exe -type <操作类型> [参数...]
--------
1. keyboard - 键盘操作
2. mouse - 鼠标操作
3. inspect - 获取控件树
3. list - 获取控件树
通用参数:
--------
@@ -927,7 +887,7 @@ sendmessage.exe -type <操作类型> [参数...]
sendmessage.exe -type keyboard -action text -window ""记事本"" -value ""Hello"" -background
5. 获取窗口控件树:
sendmessage.exe -type inspect -window ""记事本"" -filter ""button""
sendmessage.exe -type list -window ""记事本"" -filter ""button""
6. 使用句柄查找窗口:
sendmessage.exe -type keyboard -method handle -window ""0x12345"" -value ""Hello""
@@ -945,7 +905,7 @@ sendmessage.exe -type <操作类型> [参数...]
返回值:
------
1. 均为JSON格式
2. inspect操作返回控件树信息
2. list操作返回控件树信息
3. 其他操作返回操作的控件信息及其所在窗口信息
4. 失败均抛出异常
@@ -956,56 +916,4 @@ sendmessage.exe -type <操作类型> [参数...]
";
Console.WriteLine(help);
}
private static Dictionary<string, object> GetBasicWindowInfo(IntPtr hwnd, IntPtr controlHandle = default(IntPtr))
{
var info = new Dictionary<string, object>();
// 获取窗口信息
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<string, object>
{
{ "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;
}
}

View File

@@ -59,6 +59,8 @@ public class WindowManager
[DllImport("user32.dll")]
private static extern bool IsZoomed(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
@@ -242,7 +244,12 @@ public class WindowManager
switch (method.ToLower())
{
case "handle":
targetWindows.Add(new IntPtr(long.Parse(value)));
IntPtr handle = new IntPtr(long.Parse(value));
if (!IsWindow(handle))
{
throw new Exception("指定的句柄不是一个有效的窗口句柄");
}
targetWindows.Add(handle);
break;
case "active":

View File

@@ -123,7 +123,10 @@ async function runAutomation(
}
}
} catch (err) {
error = err.toString().trim();
error = err
.toString()
.replace(/^Error: /, "")
.trim();
}
if (type === "inspect") return { error };

View File

@@ -2,7 +2,7 @@ const { runCsharpFeature } = require("../../csharp");
/**
* 执行消息发送操作
* @param {string} type - 操作类型, 可选值: "keyboard"|"mouse"|"inspect"
* @param {string} type - 操作类型, 可选值: "keyboard"|"mouse"|"list"
* @param {Object} params - 参数对象
* @param {string} params.method - 查找方式:"title"|"handle"|"process"|"class"|"active"
* @param {string} params.window - 窗口标题、句柄、进程名、类名
@@ -47,7 +47,7 @@ async function runSendMessage(type, params = {}) {
}
break;
case "inspect":
case "list":
if (filter) {
args.push("-filter", filter);
}
@@ -61,54 +61,53 @@ async function runSendMessage(type, params = {}) {
try {
const result = await runCsharpFeature("sendmessage", args);
if (result) {
const jsonResult = JSON.parse(result);
if (type === "inspect") {
return jsonResult;
}
return { success: true, control: jsonResult };
const resultStr = result.toString().trim();
if (type === "list") return JSON.parse(resultStr);
if (resultStr === "true") return { success: true };
}
} catch (err) {
error = err
.toString()
.replace(/^Error: /, "")
.trim();
console.log(error);
}
if (type === "inspect") return [];
if (type === "list") return [];
return { success: false, error };
}
module.exports = {
sendKeys: (method, window, keys, options = {}) =>
sendKeys: (windowHandle, keys, options = {}) =>
runSendMessage("keyboard", {
method,
window,
method: windowHandle ? "handle" : "active",
window: windowHandle,
action: "keys",
value: keys,
options,
}),
sendText: (method, window, text, options = {}) =>
sendText: (windowHandle, text, options = {}) =>
runSendMessage("keyboard", {
method,
window,
method: windowHandle ? "handle" : "active",
window: windowHandle,
action: "text",
value: text,
options,
}),
click: (method, window, action = "click", options = {}) =>
click: (windowHandle, action = "click", options = {}) =>
runSendMessage("mouse", {
method,
window,
method: windowHandle ? "handle" : "active",
window: windowHandle,
action,
options,
}),
inspectWindow: (method, window, options = {}) =>
runSendMessage("inspect", {
method,
window,
listControls: (windowHandle, options = {}) =>
runSendMessage("list", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
options,
}),
};

View File

@@ -57,7 +57,7 @@ async function runWindow(type, params = {}) {
if (type === "info") {
return jsonResult;
}
return { success: true, window: jsonResult };
return { success: true };
}
} catch (err) {
error = err
@@ -72,27 +72,63 @@ async function runWindow(type, params = {}) {
}
module.exports = {
setTopMost: (method, window, isTopMost) =>
runWindow("topmost", { method, window, value: isTopMost }),
setTopMost: (windowHandle, isTopMost) =>
runWindow("topmost", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: isTopMost,
}),
setOpacity: (method, window, opacity) =>
runWindow("opacity", { method, window, value: opacity }),
setOpacity: (windowHandle, opacity) =>
runWindow("opacity", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: opacity,
}),
setWindowRect: (method, window, x, y, width, height) =>
runWindow("rect", { method, window, value: { x, y, width, height } }),
setWindowRect: (windowHandle, x, y, width, height) =>
runWindow("rect", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: { x, y, width, height },
}),
setWindowState: (method, window, state) =>
runWindow("state", { method, window, value: state }),
setWindowState: (windowHandle, state) =>
runWindow("state", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: state,
}),
closeWindow: (method, window) => runWindow("close", { method, window }),
closeWindow: (windowHandle) =>
runWindow("close", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
}),
setFocus: (method, window) => runWindow("focus", { method, window }),
setFocus: (windowHandle) =>
runWindow("focus", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
}),
setBorder: (method, window, hasBorder) =>
runWindow("border", { method, window, value: hasBorder }),
setBorder: (windowHandle, hasBorder) =>
runWindow("border", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: hasBorder,
}),
setClickThrough: (method, window, isTransparent) =>
runWindow("clickthrough", { method, window, value: isTransparent }),
setClickThrough: (windowHandle, isTransparent) =>
runWindow("clickthrough", {
method: windowHandle ? "handle" : "active",
window: windowHandle,
value: isTransparent,
}),
getWindowInfo: (method, window) => runWindow("info", { method, window }),
getWindowInfo: (method, window) =>
runWindow("info", {
method,
window,
}),
};

View File

@@ -177,15 +177,19 @@ const searchWindowConfig = [
},
];
const searchElementConfig = [
const windowHandleConfig = [
{
label: "窗口句柄",
component: "VariableInput",
icon: "window",
width: 12,
placeholder: "留空则使用当前活动窗口",
placeholder: "可从搜索/选择窗口获取,留空则使用当前活动窗口",
defaultValue: newVarInputVal("str", ""),
},
];
const searchElementConfig = [
windowHandleConfig,
{
label: "元素查找方式",
component: "q-select",
@@ -267,7 +271,7 @@ export const windowsCommands = {
value: "quickcomposer.windows.window.setTopMost",
label: "窗口控制",
icon: "window",
config: searchWindowConfig,
config: windowHandleConfig,
subCommands: [
{
value: "quickcomposer.windows.window.setTopMost",
@@ -630,14 +634,14 @@ export const windowsCommands = {
},
// sendmessage
{
value: "quickcomposer.windows.sendmessage.inspectWindow",
value: "quickcomposer.windows.sendmessage.listControls",
label: "发送控制消息",
icon: "smart_button",
isAsync: true,
config: searchWindowConfig,
config: windowHandleConfig,
subCommands: [
{
value: "quickcomposer.windows.sendmessage.inspectWindow",
value: "quickcomposer.windows.sendmessage.listControls",
label: "获取控件树",
icon: "account_tree",
outputVariable: "controlsTree",