feat: add a-route uia send connector

This commit is contained in:
zhaoyilun
2026-07-11 11:16:53 +08:00
parent 456ef44e8e
commit 8aa952a491
15 changed files with 941 additions and 19 deletions

View File

@@ -0,0 +1,214 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Automation;
namespace ISphereWinHelper
{
internal static class UiaSendAction
{
private const int WM_SETTEXT = 0x000C;
private const int BM_CLICK = 0x00F5;
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, string lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
public static Dictionary<string, object> SendMessage(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");
}
string editorAutomationId = HelperProtocol.GetString(args, "send_editor_automation_id", "rtbSendMessage");
string buttonAutomationId = HelperProtocol.GetString(args, "send_button_automation_id", "btnSend");
string contentText = HelperProtocol.GetString(args, "content_text", "");
string contentSha256 = HelperProtocol.GetString(args, "content_sha256", "").Trim().ToLowerInvariant();
string targetRef = HelperProtocol.GetString(args, "target_ref", "");
if (string.IsNullOrWhiteSpace(editorAutomationId))
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_CONFIG_MISSING", "send_editor_automation_id is required");
}
if (string.IsNullOrWhiteSpace(buttonAutomationId))
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_CONFIG_MISSING", "send_button_automation_id is required");
}
if (string.IsNullOrWhiteSpace(contentText))
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_CONTENT_MISSING", "content_text is required");
}
if (string.IsNullOrWhiteSpace(contentSha256) || contentSha256 != Sha256Hex(contentText))
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_HASH_MISMATCH", "content_sha256 does not match content_text");
}
try
{
AutomationElement root = AutomationElement.FromHandle(hwnd);
if (root == null)
{
return HelperProtocol.Failure(requestId, op, "WINDOW_NOT_FOUND", "no UI Automation element for hwnd");
}
AutomationElement editor = FindByAutomationId(root, editorAutomationId);
if (editor == null)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_EDITOR_NOT_FOUND", "send editor control not found: " + editorAutomationId);
}
AutomationElement button = FindByAutomationId(root, buttonAutomationId);
if (button == null)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_BUTTON_NOT_FOUND", "send button control not found: " + buttonAutomationId);
}
bool typed = SetElementText(editor, contentText);
if (!typed)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_SET_TEXT_FAILED", "could not set send editor text");
}
bool clicked = InvokeElement(button);
if (!clicked)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_CLICK_FAILED", "could not invoke send button");
}
string ackRef = "uia:" + WindowScanner.FormatHwnd(hwnd) + ":" + buttonAutomationId + ":" + ShortHash(contentSha256);
var data = new Dictionary<string, object>
{
{ "action_mode", "uia_send_message" },
{ "hwnd", WindowScanner.FormatHwnd(hwnd) },
{ "target_ref", targetRef ?? "" },
{ "content_sha256", contentSha256 },
{ "content_length", contentText.Length },
{ "editor_automation_id", editorAutomationId },
{ "button_automation_id", buttonAutomationId },
{ "editor_found", true },
{ "button_found", true },
{ "typed_text", true },
{ "clicked_ui", true },
{ "sent_message", true },
{ "uploaded_file", false },
{ "sent_file", false },
{ "captured_network", false },
{ "attached_hook", false },
{ "modified_client_data", false },
{ "ack_ref", ackRef }
};
return HelperProtocol.Success(requestId, op, data);
}
catch (Exception ex)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_FAILED", ex.Message);
}
}
private static AutomationElement FindByAutomationId(AutomationElement root, string automationId)
{
if (root == null || string.IsNullOrWhiteSpace(automationId))
{
return null;
}
try
{
return root.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, automationId));
}
catch
{
return null;
}
}
private static bool SetElementText(AutomationElement element, string text)
{
try
{
object pattern;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out pattern))
{
((ValuePattern)pattern).SetValue(text);
return true;
}
}
catch
{
}
try
{
int nativeHandle = element.Current.NativeWindowHandle;
if (nativeHandle != 0)
{
SendMessage(new IntPtr(nativeHandle), WM_SETTEXT, IntPtr.Zero, text);
return true;
}
}
catch
{
}
return false;
}
private static bool InvokeElement(AutomationElement element)
{
try
{
object pattern;
if (element.TryGetCurrentPattern(InvokePattern.Pattern, out pattern))
{
((InvokePattern)pattern).Invoke();
return true;
}
}
catch
{
}
try
{
int nativeHandle = element.Current.NativeWindowHandle;
if (nativeHandle != 0)
{
SendMessage(new IntPtr(nativeHandle), BM_CLICK, IntPtr.Zero, IntPtr.Zero);
return true;
}
}
catch
{
}
return false;
}
private static string Sha256Hex(string value)
{
using (SHA256 sha = SHA256.Create())
{
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? ""));
StringBuilder sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
}
private static string ShortHash(string value)
{
if (string.IsNullOrEmpty(value))
{
return "";
}
return value.Length <= 12 ? value : value.Substring(0, 12);
}
}
}