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

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",