Files
2026-07-05 14:29:29 +08:00

162 lines
5.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Windows.Automation;
namespace ISphereWinHelper
{
internal static class UiaDumper
{
public static Dictionary<string, object> Dump(string requestId, string op, Dictionary<string, object> args)
{
string hwndText = HelperProtocol.GetString(args, "hwnd", "");
IntPtr hwnd;
if (!WindowScanner.TryParseHwnd(hwndText, out hwnd))
{
return HelperProtocol.Failure(requestId, op, "WINDOW_NOT_FOUND", "invalid or empty hwnd");
}
int maxDepth = HelperProtocol.GetInt(args, "max_depth", 8);
if (maxDepth < 0) maxDepth = 0;
if (maxDepth > 20) maxDepth = 20;
bool includeText = HelperProtocol.GetBool(args, "include_text", true);
int maxChildren = HelperProtocol.GetInt(args, "max_children", 200);
if (maxChildren < 1) maxChildren = 1;
if (maxChildren > 1000) maxChildren = 1000;
try
{
AutomationElement rootElement = AutomationElement.FromHandle(hwnd);
if (rootElement == null)
{
return HelperProtocol.Failure(requestId, op, "WINDOW_NOT_FOUND", "no UI Automation element for hwnd");
}
var data = new Dictionary<string, object>
{
{ "hwnd", WindowScanner.FormatHwnd(hwnd) },
{ "root", DumpElement(rootElement, 0, maxDepth, includeText, maxChildren) }
};
return HelperProtocol.Success(requestId, op, data);
}
catch (Exception ex)
{
return HelperProtocol.Failure(requestId, op, "UIA_DUMP_FAILED", ex.Message);
}
}
private static Dictionary<string, object> DumpElement(AutomationElement element, int depth, int maxDepth, bool includeText, int maxChildren)
{
var node = new Dictionary<string, object>
{
{ "name", SafeString(delegate { return element.Current.Name; }) },
{ "control_type", SafeControlType(element) },
{ "automation_id", SafeString(delegate { return element.Current.AutomationId; }) },
{ "class_name", SafeString(delegate { return element.Current.ClassName; }) },
{ "localized_control_type", SafeString(delegate { return element.Current.LocalizedControlType; }) },
{ "framework_id", SafeString(delegate { return element.Current.FrameworkId; }) },
{ "is_enabled", SafeBool(delegate { return element.Current.IsEnabled; }) },
{ "is_offscreen", SafeBool(delegate { return element.Current.IsOffscreen; }) },
{ "bounds", SafeBounds(element) }
};
if (includeText)
{
node["value"] = SafeValue(element);
}
var children = new List<Dictionary<string, object>>();
if (depth < maxDepth)
{
try
{
AutomationElementCollection found = element.FindAll(TreeScope.Children, Condition.TrueCondition);
int limit = Math.Min(found.Count, maxChildren);
for (int i = 0; i < limit; i++)
{
children.Add(DumpElement(found[i], depth + 1, maxDepth, includeText, maxChildren));
}
if (found.Count > limit)
{
node["truncated_children"] = found.Count - limit;
}
}
catch
{
node["children_error"] = true;
}
}
node["children"] = children;
return node;
}
private delegate string StringGetter();
private delegate bool BoolGetter();
private static string SafeString(StringGetter getter)
{
try { return getter() ?? ""; }
catch { return ""; }
}
private static bool SafeBool(BoolGetter getter)
{
try { return getter(); }
catch { return false; }
}
private static string SafeControlType(AutomationElement element)
{
try
{
string name = element.Current.ControlType.ProgrammaticName ?? "";
return name.StartsWith("ControlType.", StringComparison.Ordinal) ? name.Substring("ControlType.".Length) : name;
}
catch
{
return "";
}
}
private static Dictionary<string, object> SafeBounds(AutomationElement element)
{
try
{
System.Windows.Rect rect = element.Current.BoundingRectangle;
return new Dictionary<string, object>
{
{ "x", rect.X },
{ "y", rect.Y },
{ "width", rect.Width },
{ "height", rect.Height }
};
}
catch
{
return new Dictionary<string, object>
{
{ "x", 0 },
{ "y", 0 },
{ "width", 0 },
{ "height", 0 }
};
}
}
private static string SafeValue(AutomationElement element)
{
try
{
object pattern;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out pattern))
{
return ((ValuePattern)pattern).Current.Value ?? "";
}
}
catch
{
}
return "";
}
}
}