Files
jiangchang-platform-kit/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs
chendelian 24384d70ba
All checks were successful
Publish Python Package to Gitea / publish (push) Successful in 32s
fix(desktop-wake): never inject SearchResultWindow2 (1.2.9)
Soft-wake search popup only; n2-wake on float crashed WeCom via wecom_wake.dll.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 21:32:23 +08:00

747 lines
31 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// desktop-wake.exe — N1 UIA 客户端 + N2 注入/钩子编排(须 x86 以匹配 WXWork)
// 用法: desktop-wake.exe <profile> <hwnd> [--mode auto|n1|n2]
// 退出码: 0暖成功 1参数 2窗无效 3UIA异常 4仍稀疏 5注入失败
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Automation;
namespace JiangchangDesktopWake
{
internal static class Program
{
private const int WM_GETOBJECT = 0x003D;
private const int WM_NULL = 0x0000;
private const uint OBJID_CLIENT = 0xFFFFFFFC;
private const uint OBJID_WINDOW = 0x00000000;
private const int SW_RESTORE = 9;
private const int SW_SHOW = 5;
private const int SW_MAXIMIZE = 3;
private const int WH_CALLWNDPROC = 4;
private const uint PROCESS_CREATE_THREAD = 0x0002;
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
private const uint PROCESS_VM_OPERATION = 0x0008;
private const uint PROCESS_VM_WRITE = 0x0020;
private const uint PROCESS_VM_READ = 0x0010;
private const uint MEM_COMMIT = 0x1000;
private const uint MEM_RESERVE = 0x2000;
private const uint PAGE_READWRITE = 0x04;
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
private static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool IsHungAppWindow(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessageTimeout(
IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
IntPtr hwnd, uint dwId, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, UIntPtr nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, UIntPtr dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize, uint dwFreeType);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, IntPtr lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibrary(string lpFileName);
private static readonly Guid IID_IAccessible = new Guid("618736E0-3C3D-11CF-810C-00AA00389B71");
private static int Main(string[] args)
{
if (args.Length < 2)
{
Console.Error.WriteLine("usage: desktop-wake.exe <profile> <hwnd> [--mode auto|n1|n2|n2-wake|n2-hook]");
Console.Error.WriteLine(" n1=UIA; n2=inject only; n2-wake=inject+WakeProvider(C2); n2-hook=UNSAFE");
return 1;
}
string profile = (args[0] ?? "").Trim().ToLowerInvariant();
string mode = "auto";
for (int i = 2; i < args.Length; i++)
{
if (args[i] == "--mode" && i + 1 < args.Length)
{
mode = (args[i + 1] ?? "auto").Trim().ToLowerInvariant();
i++;
}
}
IntPtr hwnd;
try { hwnd = ParseHwnd(args[1]); }
catch
{
Console.Error.WriteLine("invalid hwnd: " + args[1]);
return 1;
}
if (!IsWindow(hwnd))
{
WriteJson(false, false, false, profile, hwnd, 0, 0, new string[0], "window_invalid", mode, null);
return 2;
}
int minCount = profile == "wecom" ? 80 : 30;
string[] markers = profile == "wecom"
? new[] { "con_op", "searchedit" }
: new string[0];
string method = "n1_uia_client";
string detailExtra = "";
bool n2InjectOk = false;
try
{
Activate(hwnd);
// n1/auto:进程外 UIA
// n2:仅注入探针
// n2-wake:注入 + 远程 WakeProvider(C2,无钩子)
// n2-hook:UNSAFE
bool doN2 = (mode == "n2" || mode == "n2-wake" || mode == "n2-hook") && profile == "wecom";
bool doN2Hook = (mode == "n2-hook") && profile == "wecom";
bool doWakeProvider = (mode == "n2-wake") && profile == "wecom";
// n2-wake:不在 exe 内做 UIA 深扫(易跟着目标进程一起挂);暖树由 Python probe 判定
bool doN1 = (mode == "n1" || mode == "auto" || mode == "n2-hook");
// n2-wake 不再 SPI_SETSCREENREADER:会触发 UiaRoot 查询,测试机曾崩 uiautomationcore
if (!doWakeProvider)
EnableScreenReaderFlag();
// 1.2.9:SearchResultWindow2 禁止 n2 注入(wecom_wake.dll 子类化/DuiLib 会崩企微)
if (doN2 && IsSearchResultPopup(hwnd))
{
SendGetObject(hwnd);
TryMsaa(hwnd);
bool responsivePopup = CheckUiResponsive(hwnd);
method = "popup_soft_only";
WriteJson(
responsivePopup, false, responsivePopup,
profile, hwnd, 0, 0, new string[0],
"search_popup_no_inject|soft_getobject_msaa",
mode, method);
return responsivePopup ? 0 : 6;
}
if (doN2)
{
string n2Detail;
IntPtr remoteMod;
n2InjectOk = TryN2(hwnd, doN2Hook, doWakeProvider, out n2Detail, out remoteMod);
detailExtra = n2Detail;
method = n2InjectOk ? (doWakeProvider ? "n2_wake" : "n2_inject") : "n2_inject_failed";
bool responsive = CheckUiResponsive(hwnd);
detailExtra = detailExtra + ";ui_responsive=" + responsive;
if (mode == "n2" || mode == "n2-wake")
{
// ok=注入(+provider)成功且 UI 仍响应;是否暖树由调用方 probe
WriteJson(
n2InjectOk && responsive, n2InjectOk, responsive,
profile, hwnd, 0, 0, new string[0],
(n2InjectOk ? (doWakeProvider ? "wake_provider_ok" : "inject_ok") : "inject_failed")
+ "|" + detailExtra,
mode, method);
if (!n2InjectOk) return 5;
if (!responsive) return 6;
return 0;
}
if (!n2InjectOk)
{
WriteJson(false, false, responsive, profile, hwnd, 0, 0, new string[0], n2Detail, mode, method);
return 5;
}
if (!responsive)
{
WriteJson(false, true, false, profile, hwnd, 0, 0, new string[0],
"ui_hung_after_inject|" + detailExtra, mode, method);
return 6;
}
Thread.Sleep(400);
}
if (doN1)
{
SendGetObject(hwnd);
TryMsaa(hwnd);
var root = AutomationElement.FromHandle(hwnd);
if (root == null)
{
WriteJson(false, n2InjectOk, CheckUiResponsive(hwnd), profile, hwnd, 0, 0, new string[0],
"uia_from_handle_null|" + detailExtra, mode, method);
return 3;
}
try { root.SetFocus(); } catch { }
int walk1 = WalkCount(root, 64);
int findAll = FindAllCount(root);
Thread.Sleep(200);
SendGetObject(hwnd);
int walk2 = WalkCount(root, 64);
int walk = Math.Max(walk1, Math.Max(walk2, findAll));
var foundMarkers = FindMarkers(root, markers);
bool warm = walk >= minCount
&& (markers.Length == 0 || foundMarkers.Count >= markers.Length);
bool responsive = CheckUiResponsive(hwnd);
string detail = warm ? "warm" : "still_sparse";
if (!string.IsNullOrEmpty(detailExtra))
detail = detail + "|" + detailExtra;
if (mode == "n2-wake")
method = warm ? "n2_wake" : "n2_wake_still_sparse";
else if (mode == "n2-hook")
method = n2InjectOk ? (warm ? "n2_hook" : "n2_hook_still_sparse") : method;
WriteJson(warm, n2InjectOk, responsive, profile, hwnd, walk, findAll,
foundMarkers.ToArray(), detail, mode, method);
return warm ? 0 : 4;
}
WriteJson(false, false, false, profile, hwnd, 0, 0, new string[0], "no_mode", mode, method);
return 1;
}
catch (Exception ex)
{
WriteJson(false, n2InjectOk, false, profile, hwnd, 0, 0, new string[0],
"exception:" + ex.GetType().Name + ":" + ex.Message + "|" + detailExtra, mode, method);
return 3;
}
}
private static bool CheckUiResponsive(IntPtr hwnd)
{
try
{
if (IsHungAppWindow(hwnd)) return false;
IntPtr result;
IntPtr ok = SendMessageTimeout(hwnd, WM_NULL, IntPtr.Zero, IntPtr.Zero, 0x0002, 2000, out result);
return ok != IntPtr.Zero;
}
catch { return false; }
}
private static void EnableScreenReaderFlag()
{
try
{
// SPI_SETSCREENREADER=0x46
SystemParametersInfo(0x0046, 1, IntPtr.Zero, 0x0003);
}
catch { }
}
private static bool TryN2(IntPtr hwnd, bool enableHook, bool callWakeProvider, out string detail, out IntPtr remoteModule)
{
remoteModule = IntPtr.Zero;
string dll = FindPayloadDll();
if (dll == null)
{
detail = "n2_dll_missing";
return false;
}
uint pid;
uint tid = GetWindowThreadProcessId(hwnd, out pid);
if (pid == 0 || tid == 0)
{
detail = "n2_pid_tid_failed";
return false;
}
string targetFile = Path.Combine(Path.GetTempPath(), "jiangchang_desktop_wake_target.txt");
File.WriteAllText(
targetFile,
"hwnd=0x" + hwnd.ToInt64().ToString("x") + "\r\npid=" + pid + "\r\n",
Encoding.ASCII);
string loadedFile = Path.Combine(Path.GetTempPath(), "jiangchang_desktop_wake_loaded.txt");
string subclassFile = Path.Combine(Path.GetTempPath(), "jiangchang_desktop_wake_subclass.txt");
string hookFile = Path.Combine(Path.GetTempPath(), "jiangchang_desktop_wake_hook.txt");
string providerFile = Path.Combine(Path.GetTempPath(), "jiangchang_desktop_wake_provider.txt");
try { if (File.Exists(loadedFile)) File.Delete(loadedFile); } catch { }
try { if (File.Exists(subclassFile)) File.Delete(subclassFile); } catch { }
try { if (File.Exists(hookFile)) File.Delete(hookFile); } catch { }
try { if (File.Exists(providerFile)) File.Delete(providerFile); } catch { }
string injErr;
bool injected = InjectLoadLibrary(pid, dll, out injErr, out remoteModule);
string hookErr = enableHook ? "" : "skipped_unsafe_hook";
bool hooked = false;
if (enableHook)
{
hooked = InstallCallWndHook(dll, tid, hwnd, out hookErr);
PostMessage(hwnd, WM_NULL, IntPtr.Zero, IntPtr.Zero);
Thread.Sleep(200);
}
string wakeErr = "skipped";
bool wakeCalled = false;
if (callWakeProvider && injected && remoteModule != IntPtr.Zero)
{
wakeCalled = CallRemoteWakeProvider(pid, dll, remoteModule, hwnd, out wakeErr);
// STA UIA 可能写完 marker 略晚;等 done=1
for (int i = 0; i < 40; i++)
{
if (File.Exists(providerFile))
{
try
{
string pt = File.ReadAllText(providerFile);
if (pt.IndexOf("done=1", StringComparison.Ordinal) >= 0)
{
wakeCalled = true;
break;
}
}
catch { }
}
Thread.Sleep(250);
}
if (!wakeCalled && File.Exists(providerFile))
wakeCalled = true;
}
bool loadedMarker = false;
if (File.Exists(loadedFile))
{
try
{
string text = File.ReadAllText(loadedFile);
loadedMarker = text.IndexOf("pid=" + pid, StringComparison.Ordinal) >= 0
|| text.IndexOf("match=1", StringComparison.Ordinal) >= 0;
}
catch { loadedMarker = File.Exists(loadedFile); }
}
bool subclassMarker = File.Exists(subclassFile);
bool hookMarker = File.Exists(hookFile);
bool providerMarker = File.Exists(providerFile);
string providerText = "";
if (providerMarker)
{
try { providerText = File.ReadAllText(providerFile).Replace("\r", " ").Replace("\n", " ").Trim(); }
catch { }
}
detail = string.Format(
"pid={0};tid={1};inject={2};hook={3};wake={4};loaded_marker={5};provider_marker={6};provider={7};subclass_marker={8};hook_marker={9};injErr={10};hookErr={11};wakeErr={12};dll={13};remoteMod=0x{14}",
pid, tid, injected, hooked, wakeCalled, loadedMarker, providerMarker, providerText,
subclassMarker, hookMarker, injErr, hookErr, wakeErr, Path.GetFileName(dll),
remoteModule.ToInt64().ToString("x"));
// DLL 已在目标进程时再次 LoadLibrary 不会重跑 DllMain,故 remoteMod!=0 也算注入成功
bool injectOk = injected && (loadedMarker || remoteModule != IntPtr.Zero);
// WakeProvider 为 best-effort:注入成功即可,暖树由外层 probe 判定
return injectOk || (hooked && (hookMarker || subclassMarker));
}
private static string FindPayloadDll()
{
try
{
string exe = Process.GetCurrentProcess().MainModule.FileName;
var dir = new DirectoryInfo(Path.GetDirectoryName(exe));
for (int i = 0; i < 6 && dir != null; i++)
{
string newest = null;
DateTime best = DateTime.MinValue;
string[] searchDirs = new string[] { dir.FullName, Path.Combine(dir.FullName, "native") };
foreach (var sd in searchDirs)
{
if (!Directory.Exists(sd)) continue;
// 优先正式名 wecom_wake.dll(发版包)
string primary = Path.Combine(sd, "wecom_wake.dll");
if (File.Exists(primary)) return Path.GetFullPath(primary);
foreach (var f in Directory.GetFiles(sd, "wecom_wake*.dll"))
{
DateTime t = File.GetLastWriteTimeUtc(f);
if (t >= best) { best = t; newest = f; }
}
}
if (newest != null) return Path.GetFullPath(newest);
dir = dir.Parent;
}
}
catch { }
return null;
}
private static bool InjectLoadLibrary(uint pid, string dllPath, out string err, out IntPtr remoteModule)
{
err = "";
remoteModule = IntPtr.Zero;
IntPtr hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
false, pid);
if (hProcess == IntPtr.Zero)
{
err = "OpenProcess:" + Marshal.GetLastWin32Error();
return false;
}
try
{
byte[] pathBytes = Encoding.Unicode.GetBytes(dllPath + "\0");
IntPtr remote = VirtualAllocEx(hProcess, IntPtr.Zero, (UIntPtr)pathBytes.Length, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (remote == IntPtr.Zero)
{
err = "VirtualAllocEx:" + Marshal.GetLastWin32Error();
return false;
}
UIntPtr written;
if (!WriteProcessMemory(hProcess, remote, pathBytes, (UIntPtr)pathBytes.Length, out written))
{
err = "WriteProcessMemory:" + Marshal.GetLastWin32Error();
return false;
}
IntPtr hKernel = GetModuleHandle("kernel32.dll");
IntPtr loadLib = GetProcAddress(hKernel, "LoadLibraryW");
if (loadLib == IntPtr.Zero)
{
err = "GetProcAddress LoadLibraryW failed";
return false;
}
IntPtr hThread = CreateRemoteThread(hProcess, IntPtr.Zero, UIntPtr.Zero, loadLib, remote, 0, IntPtr.Zero);
if (hThread == IntPtr.Zero)
{
err = "CreateRemoteThread:" + Marshal.GetLastWin32Error();
return false;
}
uint wait = WaitForSingleObject(hThread, 5000);
uint exitCode = 0;
GetExitCodeThread(hThread, out exitCode);
CloseHandle(hThread);
VirtualFreeEx(hProcess, remote, UIntPtr.Zero, 0x8000);
if (wait != 0)
{
err = "LoadLibrary wait timeout/status=" + wait;
return false;
}
if (exitCode == 0)
{
err = "LoadLibrary returned NULL";
return false;
}
remoteModule = new IntPtr(unchecked((int)exitCode)); // x86 HMODULE
return true;
}
finally
{
CloseHandle(hProcess);
}
}
private static bool CallRemoteWakeProvider(
uint pid, string dllPath, IntPtr remoteModule, IntPtr hwnd, out string err)
{
err = "";
IntPtr localMod = LoadLibrary(dllPath);
if (localMod == IntPtr.Zero)
{
err = "local LoadLibrary:" + Marshal.GetLastWin32Error();
return false;
}
IntPtr localProc = GetProcAddress(localMod, "WakeProvider");
if (localProc == IntPtr.Zero)
localProc = GetProcAddress(localMod, "_WakeProvider@4");
if (localProc == IntPtr.Zero)
{
err = "WakeProvider export missing";
return false;
}
long offset = localProc.ToInt64() - localMod.ToInt64();
IntPtr remoteProc = new IntPtr(remoteModule.ToInt64() + offset);
IntPtr hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
false, pid);
if (hProcess == IntPtr.Zero)
{
err = "OpenProcess wake:" + Marshal.GetLastWin32Error();
return false;
}
try
{
IntPtr hThread = CreateRemoteThread(
hProcess, IntPtr.Zero, UIntPtr.Zero, remoteProc, hwnd, 0, IntPtr.Zero);
if (hThread == IntPtr.Zero)
{
err = "CreateRemoteThread WakeProvider:" + Marshal.GetLastWin32Error();
return false;
}
// STA + 进程内 FindAll 可能较慢
uint wait = WaitForSingleObject(hThread, 20000);
CloseHandle(hThread);
if (wait != 0)
{
err = "WakeProvider wait=" + wait;
return false;
}
return true;
}
finally
{
CloseHandle(hProcess);
}
}
private static bool InstallCallWndHook(string dllPath, uint tid, IntPtr hwnd, out string err)
{
err = "";
IntPtr hMod = LoadLibrary(dllPath);
if (hMod == IntPtr.Zero)
{
err = "LoadLibrary:" + Marshal.GetLastWin32Error();
return false;
}
// x86 __stdcall 可能是 _JcCallWndProc@12;优先无修饰名(.def 导出)
IntPtr proc = GetProcAddress(hMod, "JcCallWndProc");
if (proc == IntPtr.Zero)
proc = GetProcAddress(hMod, "_JcCallWndProc@12");
if (proc == IntPtr.Zero)
{
err = "GetProcAddress JcCallWndProc:" + Marshal.GetLastWin32Error();
return false;
}
IntPtr hhk = SetWindowsHookEx(WH_CALLWNDPROC, proc, hMod, tid);
if (hhk == IntPtr.Zero)
{
err = "SetWindowsHookEx:" + Marshal.GetLastWin32Error();
return false;
}
PostMessage(hwnd, WM_NULL, IntPtr.Zero, IntPtr.Zero);
Thread.Sleep(400);
// 保持钩子,避免 DLL 被立刻卸载;进程退出时清理
GC.KeepAlive(hhk);
return true;
}
private static IntPtr ParseHwnd(string s)
{
s = (s ?? "").Trim();
if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
return new IntPtr(Convert.ToInt64(s, 16));
// 含 a-f 一定是 hex;纯数字优先按十进制(Python 可传 {hwnd})
bool hasHexLetter = false;
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
if ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { hasHexLetter = true; break; }
}
if (hasHexLetter)
return new IntPtr(Convert.ToInt64(s, 16));
long asDec;
if (long.TryParse(s, out asDec))
return new IntPtr(asDec);
return new IntPtr(Convert.ToInt64(s, 16));
}
private static bool IsSearchResultPopup(IntPtr hwnd)
{
try
{
var sb = new StringBuilder(256);
if (GetClassName(hwnd, sb, sb.Capacity) <= 0) return false;
return string.Equals(sb.ToString(), "SearchResultWindow2", StringComparison.Ordinal);
}
catch { return false; }
}
/// <summary>
/// 主窗可 Maximize;SearchResultWindow2 等浮层禁止 Maximize(会撑爆/关掉浮层)。
/// </summary>
private static bool ShouldMaximize(IntPtr hwnd)
{
try
{
var sb = new StringBuilder(256);
if (GetClassName(hwnd, sb, sb.Capacity) <= 0) return true;
string cls = sb.ToString();
if (string.Equals(cls, "SearchResultWindow2", StringComparison.Ordinal))
return false;
if (cls.IndexOf("Shadow", StringComparison.OrdinalIgnoreCase) >= 0)
return false;
}
catch { }
return true;
}
private static void Activate(IntPtr hwnd)
{
try
{
if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE);
else if (ShouldMaximize(hwnd)) ShowWindow(hwnd, SW_MAXIMIZE);
else ShowWindow(hwnd, SW_SHOW);
SetForegroundWindow(hwnd);
Thread.Sleep(250);
}
catch { }
}
private static void SendGetObject(IntPtr hwnd)
{
SendMessage(hwnd, WM_GETOBJECT, IntPtr.Zero, new IntPtr(unchecked((int)OBJID_CLIENT)));
SendMessage(hwnd, WM_GETOBJECT, IntPtr.Zero, new IntPtr(unchecked((int)OBJID_WINDOW)));
}
private static void TryMsaa(IntPtr hwnd)
{
try
{
Guid iid = IID_IAccessible;
object acc;
int hr = AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, ref iid, out acc);
if (hr == 0 && acc != null)
Marshal.FinalReleaseComObject(acc);
}
catch { }
}
private static int WalkCount(AutomationElement root, int maxDepth)
{
int count = 0;
var walker = TreeWalker.ControlViewWalker;
Walk(walker, root, 0, maxDepth, ref count);
return count;
}
private static void Walk(TreeWalker walker, AutomationElement node, int depth, int maxDepth, ref int count)
{
if (node == null || depth > maxDepth) return;
count++;
AutomationElement child;
try { child = walker.GetFirstChild(node); }
catch { return; }
while (child != null)
{
Walk(walker, child, depth + 1, maxDepth, ref count);
try { child = walker.GetNextSibling(child); }
catch { break; }
}
}
private static int FindAllCount(AutomationElement root)
{
try
{
var all = root.FindAll(TreeScope.Subtree, Condition.TrueCondition);
return all == null ? 0 : all.Count;
}
catch { return 0; }
}
private static List<string> FindMarkers(AutomationElement root, string[] markers)
{
var found = new List<string>();
if (markers == null || markers.Length == 0) return found;
foreach (var m in markers)
{
try
{
var el = root.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.NameProperty, m));
if (el != null) { found.Add(m); continue; }
el = root.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.AutomationIdProperty, m));
if (el != null) found.Add(m);
}
catch { }
}
return found;
}
private static void WriteJson(
bool ok, bool injectOk, bool uiResponsive, string profile, IntPtr hwnd,
int walk, int findAll, string[] markers, string detail, string mode, string method)
{
var sb = new StringBuilder();
sb.Append("{");
sb.Append("\"ok\":").Append(ok ? "true" : "false").Append(",");
sb.Append("\"inject_ok\":").Append(injectOk ? "true" : "false").Append(",");
sb.Append("\"ui_responsive\":").Append(uiResponsive ? "true" : "false").Append(",");
sb.Append("\"profile\":\"").Append(Escape(profile)).Append("\",");
sb.Append("\"hwnd\":\"").Append(hwnd.ToInt64().ToString("x")).Append("\",");
sb.Append("\"mode\":\"").Append(Escape(mode ?? "")).Append("\",");
sb.Append("\"method\":\"").Append(Escape(method ?? "")).Append("\",");
sb.Append("\"walk_count\":").Append(walk).Append(",");
sb.Append("\"findall_count\":").Append(findAll).Append(",");
sb.Append("\"markers_found\":[");
for (int i = 0; i < markers.Length; i++)
{
if (i > 0) sb.Append(",");
sb.Append("\"").Append(Escape(markers[i])).Append("\"");
}
sb.Append("],");
sb.Append("\"detail\":\"").Append(Escape(detail)).Append("\"");
sb.Append("}");
Console.WriteLine(sb.ToString());
}
private static string Escape(string s)
{
if (string.IsNullOrEmpty(s)) return "";
return s.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
}
}