diff --git a/pyproject.toml b/pyproject.toml index d0d5904..22cef46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jiangchang-platform-kit" -version = "1.2.4" +version = "1.2.5" description = "匠厂平台共享组件:Skill 实体 SDK + 桌面应用自动化 SDK + 桌面 UIA 暖树" readme = "README.md" requires-python = ">=3.10" diff --git a/src/jiangchang_desktop_wake/ACCEPTANCE.md b/src/jiangchang_desktop_wake/ACCEPTANCE.md index 0149b6d..e048937 100644 --- a/src/jiangchang_desktop_wake/ACCEPTANCE.md +++ b/src/jiangchang_desktop_wake/ACCEPTANCE.md @@ -1,7 +1,7 @@ -# jiangchang_desktop_wake 验收(ensure 主路径,1.2.4+) +# jiangchang_desktop_wake 验收(ensure 主路径,1.2.5+) 正式对外路径是 **`ensure`**(内部:`activate` → `desktop-wake --mode n2-wake` → `probe`)。 -`n2-wake` **不再**在企微进程内跑 UIAutomation FindAll(防 `uiautomationcore.dll` 崩溃);判暖只看进程外 probe。 +`n2-wake`:**仅 OBJID_CLIENT** 走 DuiLib;不做进程内 FindAll、不同步 GETOBJECT、不 SPI 读屏;判暖只看进程外 probe。 `c2` / `inject` 仅诊断用。 ## 0. 编译(企微先完全退出,含托盘) @@ -57,7 +57,7 @@ python -m jiangchang_desktop_wake probe --profile wecom | `probe_after.findall_count` | **≥ 80**(常见 300+) | | markers | 含 `con_op`、`searchedit` | | 企微 UI | 可点,无「未响应」 | -| provider | `done=1` `subclass=1` `dui=1` `no_inproc_uia=1`;`lr_client≠0` 更佳 | +| provider | `done=1` `subclass=1` `dui=1` `client_only=1` `no_inproc_uia=1` | | subclass | `method=SetWindowLongPtr`,`ok=1`,`userdata≠0` | | 企微进程 | **不崩**、无「错误报告」对话框 | diff --git a/src/jiangchang_desktop_wake/README.md b/src/jiangchang_desktop_wake/README.md index ee8f910..20a6717 100644 --- a/src/jiangchang_desktop_wake/README.md +++ b/src/jiangchang_desktop_wake/README.md @@ -2,8 +2,8 @@ 桌面 UIA Provider **暖树**(与 `jiangchang_skill_core` / `jiangchang_desktop_sdk` 平级)。 -企微主路径:注入 `wecom_wake.dll`,对主窗 `SetWindowLongPtr` 后走 DuiLib -`GetAccessible` / `OnGetObject`;**不在进程内 FindAll**(1.2.4+,避免 uiautomationcore 崩溃)。 +企微主路径:注入 `wecom_wake.dll`,对主窗 `SetWindowLongPtr` 后仅对 +`OBJID_CLIENT` 走 DuiLib `GetAccessible`(1.2.5+;避免 UiaRoot/MSAA 混用崩 uiautomationcore)。 可整目录删除:`src/jiangchang_desktop_wake` + `tests/desktop_wake`。 diff --git a/src/jiangchang_desktop_wake/native/desktop-wake.exe b/src/jiangchang_desktop_wake/native/desktop-wake.exe index 81a4f6b..591b305 100644 Binary files a/src/jiangchang_desktop_wake/native/desktop-wake.exe and b/src/jiangchang_desktop_wake/native/desktop-wake.exe differ diff --git a/src/jiangchang_desktop_wake/native/wecom_wake.dll b/src/jiangchang_desktop_wake/native/wecom_wake.dll index 63bc320..4b83986 100644 Binary files a/src/jiangchang_desktop_wake/native/wecom_wake.dll and b/src/jiangchang_desktop_wake/native/wecom_wake.dll differ diff --git a/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs b/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs index e0cfc83..3f909a3 100644 --- a/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs +++ b/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs @@ -152,8 +152,6 @@ namespace JiangchangDesktopWake try { Activate(hwnd); - EnableScreenReaderFlag(); // 进程外 SPI:部分 App 见“读屏器”会挂树 - // n1/auto:进程外 UIA // n2:仅注入探针 // n2-wake:注入 + 远程 WakeProvider(C2,无钩子) @@ -163,6 +161,9 @@ namespace JiangchangDesktopWake 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(); if (doN2) { diff --git a/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp b/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp index 94389c5..3178c09 100644 --- a/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp +++ b/src/jiangchang_desktop_wake/native_src/N2/payload/wecom_wake.cpp @@ -6,10 +6,11 @@ * WakeTarget — 旧子类化(危险,仅 n2-hook) * JcCallWndProc — 旧钩子(危险,仅 n2-hook) * - * 1.2.4 主路径(防 uiautomationcore 崩): - * - SetWindowLongPtr 子类化,WM_GETOBJECT → DuiLib GetAccessible/OnGetObject - * - 重入守卫:处理 GETOBJECT 时不再二次进 DuiLib - * - 不做进程内 UIAutomation FindAll / AccessibleObjectFromWindow(判暖交给进程外 probe) + * 1.2.5 主路径(防 uiautomationcore 崩): + * - SetWindowLongPtr 子类化;仅 OBJID_CLIENT 走 DuiLib GetAccessible + * - UiaRootObjectId 等原样交给旧 WndProc(禁止用 MSAA Lresult 冒充 UIA root) + * - 不做进程内 UIAutomation FindAll;不做同步 SendMessage(GETOBJECT) + * - 重入守卫;判暖交给进程外 probe */ #define WIN32_LEAN_AND_MEAN #include @@ -122,11 +123,9 @@ static HWND ReadTargetHwnd() static void NudgeWinEvents(HWND hwnd) { + /* 只异步点 CLIENT,绝不 Post UiaRootObjectId(易进 uiautomationcore 崩) */ NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, CHILDID_SELF); - NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT, CHILDID_SELF); PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_CLIENT); - PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_WINDOW); - PostMessageW(hwnd, WM_GETOBJECT, 0, (LPARAM)UiaRootObjectId); PostMessageW(hwnd, WM_NULL, 0, 0); } @@ -144,8 +143,16 @@ static bool ResolveDuiLib() return g_fnGetAccessible != NULL || g_fnOnGetObject != NULL; } +static bool IsClientObjectId(LPARAM lParam) +{ + return (LONG)lParam == (LONG)OBJID_CLIENT; +} + static LRESULT TryDuiLibGetObject(HWND hwnd, WPARAM wParam, LPARAM lParam) { + /* 只服务 MSAA CLIENT。UiaRootObjectId 用 LresultFromObject(IAccessible) 会搞崩 UIA core */ + if (!IsClientObjectId(lParam)) + return 0; if (!ResolveDuiLib()) return 0; @@ -194,7 +201,8 @@ static LRESULT HandleGetObject( else if (oldProc) r = CallWindowProcW(oldProc, hwnd, WM_GETOBJECT, wParam, lParam); - if (r == 0) + /* 非 CLIENT(含 UiaRootObjectId):绝不伪造返回值 */ + if (r == 0 && IsClientObjectId(lParam)) r = TryDuiLibGetObject(hwnd, wParam, lParam); t_inGetObject = 0; @@ -263,11 +271,10 @@ static bool InstallDuiSubclass(HWND hwnd) return true; } -/* 只装子类化 + 异步 nudge;可选一次短超时 GETOBJECT 看 lr(无进程内 UIA) */ +/* 只装子类化 + 异步 nudge;禁止同步 SendMessage(GETOBJECT) */ static void DoSafeWake(HWND hwnd) { char line[512]; - LRESULT lrClient = 0; int subclass_ok = 0; int dui_ok = 0; @@ -276,21 +283,13 @@ static void DoSafeWake(HWND hwnd) subclass_ok = InstallDuiSubclass(hwnd) ? 1 : 0; NudgeWinEvents(hwnd); - - DWORD_PTR smResult = 0; - if (SendMessageTimeoutW( - hwnd, WM_GETOBJECT, 0, (LPARAM)OBJID_CLIENT, - SMTO_ABORTIFHUNG, 2000, &smResult)) { - lrClient = (LRESULT)smResult; - } - NudgeWinEvents(hwnd); sprintf_s(line, - "hwnd=%p sta_tid=%lu lr_client=%ld subclass=%d dui=%d " + "hwnd=%p sta_tid=%lu subclass=%d dui=%d " "dui_hits=%ld getobject_hits=%ld reenter_skip=%ld " - "no_inproc_uia=1 done=1\r\n", - hwnd, GetCurrentThreadId(), (long)lrClient, subclass_ok, dui_ok, + "client_only=1 no_sync_getobject=1 no_inproc_uia=1 done=1\r\n", + hwnd, GetCurrentThreadId(), subclass_ok, dui_ok, (long)g_duiHits, (long)g_getobjectHits, (long)g_reenterSkip); WriteMarker("jiangchang_desktop_wake_provider.txt", line); }