diff --git a/pyproject.toml b/pyproject.toml index be12128..cbd0339 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "jiangchang-platform-kit" -version = "1.2.8" +version = "1.2.9" 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 3f0ceea..b9a8d2d 100644 --- a/src/jiangchang_desktop_wake/ACCEPTANCE.md +++ b/src/jiangchang_desktop_wake/ACCEPTANCE.md @@ -1,4 +1,4 @@ -# jiangchang_desktop_wake 验收(ensure 主路径,1.2.8+) +# jiangchang_desktop_wake 验收(ensure 主路径,1.2.9+) 正式对外路径是 **`ensure`**(内部:`activate` → `desktop-wake --mode n2-wake` → `probe`)。 `n2-wake`:**仅 OBJID_CLIENT** 走 DuiLib;不做进程内 FindAll、不同步 GETOBJECT、不 SPI 读屏;判暖只看进程外 probe。 @@ -7,7 +7,7 @@ | profile | 窗 | 说明 | |---|---|---| | `wecom` | `WeWorkWindow` | 主窗(原有) | -| `wecom_search` | `SearchResultWindow2` | 搜索浮层;**不对浮层 Maximize**;n2-wake **子类化浮层本身**(DLL 支持多窗;主窗已暖后仍可再装浮层) | +| `wecom_search` | `SearchResultWindow2` | 搜索浮层;**不对浮层 Maximize / 不注入**(1.2.9+ 仅软唤醒;注入会崩企微) | ## 0. 编译(企微先完全退出,含托盘) @@ -77,11 +77,11 @@ python -m jiangchang_desktop_wake probe --profile wecom --- -## C. 搜索浮层 wecom_search(1.2.8+) +## C. 搜索浮层 wecom_search(1.2.9+) 1. **建议先** `ensure --profile wecom` 暖主窗(可与浮层分步)。 2. 在搜索框输入联系人,**保持搜索结果浮层打开**(类名 `SearchResultWindow2`)。 -3. 验收: +3. 验收(**禁止**对浮层 n2-wake/注入;profile 仅 `wm_getobject` + `msaa`): ```powershell cd D:\OpenClaw\client-jiangchang\jiangchang-platform-kit @@ -93,10 +93,10 @@ python -m jiangchang_desktop_wake probe --profile wecom_search | 期望 | 说明 | |---|---| -| ensure 退出码 `0` | 浮层已暖 | -| `warm=true` | markers 含 `search_result` / `user_item`(或 findall 足够) | -| `%TEMP%\jiangchang_desktop_wake_subclass.txt` | 含浮层 hwnd,`ok=1`(主窗已暖后再 ensure 浮层也会再写一条) | -| 企微 UI | 浮层**不**被 Maximize 撑满、**不白屏**、不无故关闭 | +| ensure **不**崩企微 | 故障模块不应再是 `wecom_wake.dll` | +| `warm=true`(或 soft poke 后 findall≫2) | markers 含 `search_result` / `user_item` | +| 企微 UI | 浮层保持打开、**不** Maximize、不白屏 | +| 若手动对浮层跑 `desktop-wake … n2-wake` | exe/DLL 应走 `popup_soft_only` / `popup_nudge_only`,**不**子类化浮层 | | `WINDOW_NOT_FOUND` | 浮层未打开——先展开搜索结果再测 | --- diff --git a/src/jiangchang_desktop_wake/README.md b/src/jiangchang_desktop_wake/README.md index 6e5c9d0..8c9ed6a 100644 --- a/src/jiangchang_desktop_wake/README.md +++ b/src/jiangchang_desktop_wake/README.md @@ -42,7 +42,7 @@ print(rs.warm, rs.probe_after.findall_count, rs.probe_after.markers_found) | profile | 窗 | 说明 | |---|---|---| | `wecom` | `WeWorkWindow` | 主窗;markers=`con_op`,`searchedit` | -| `wecom_search` | `SearchResultWindow2` | 搜索浮层;markers=`search_result`,`user_item`;不 Maximize;**n2-wake 对浮层子类化**(1.2.8+ 支持主窗+浮层多窗) | +| `wecom_search` | `SearchResultWindow2` | 搜索浮层;markers=`search_result`,`user_item`;**禁止注入/n2-wake**(1.2.9+ 仅进程外软唤醒) | | CLI | 退出码 | |---|---| diff --git a/src/jiangchang_desktop_wake/native/desktop-wake.exe b/src/jiangchang_desktop_wake/native/desktop-wake.exe index 9754597..6a19f59 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 21fa432..741fd32 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 2c582c4..028d096 100644 --- a/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs +++ b/src/jiangchang_desktop_wake/native_src/DesktopWake/Program.cs @@ -169,6 +169,21 @@ namespace JiangchangDesktopWake 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; @@ -582,6 +597,17 @@ namespace JiangchangDesktopWake 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; } + } + /// /// 主窗可 Maximize;SearchResultWindow2 等浮层禁止 Maximize(会撑爆/关掉浮层)。 /// 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 12f3ae3..d80ea40 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 @@ -12,6 +12,7 @@ * - 不做进程内 UIAutomation FindAll;不做同步 SendMessage(GETOBJECT) * - 重入守卫;判暖交给进程外 probe * 1.2.8:支持多 HWND 子类化(主窗 + SearchResultWindow2),禁止「全局只装一次」。 + * 1.2.9:SearchResultWindow2 禁止子类化/DuiLib thiscall(会崩企微);浮层只异步 nudge。 */ #define WIN32_LEAN_AND_MEAN #include @@ -139,6 +140,14 @@ static void NudgeWinEvents(HWND hwnd) PostMessageW(hwnd, WM_NULL, 0, 0); } +static bool IsSearchResultPopup(HWND hwnd) +{ + wchar_t cls[256]; + if (!hwnd || !GetClassNameW(hwnd, cls, 256)) + return false; + return _wcsicmp(cls, L"SearchResultWindow2") == 0; +} + static bool ResolveDuiLib() { if (g_fnGetAccessible && g_fnOnGetObject) return true; @@ -322,6 +331,19 @@ static void DoSafeWake(HWND hwnd) int subclass_ok = 0; int dui_ok = 0; + /* 浮层:DuiLib thiscall / 换 WndProc 在宿主环境会直接崩企微(fault=wecom_wake.dll) */ + if (IsSearchResultPopup(hwnd)) { + NudgeWinEvents(hwnd); + NudgeWinEvents(hwnd); + sprintf_s(line, + "hwnd=%p sta_tid=%lu popup_nudge_only=1 subclass=0 dui=0 " + "dui_hits=%ld getobject_hits=%ld done=1\r\n", + hwnd, GetCurrentThreadId(), + (long)g_duiHits, (long)g_getobjectHits); + WriteMarker("jiangchang_desktop_wake_provider.txt", line); + return; + } + ResolveDuiLib(); dui_ok = (g_fnGetAccessible || g_fnOnGetObject) ? 1 : 0; subclass_ok = InstallDuiSubclass(hwnd) ? 1 : 0; diff --git a/src/jiangchang_desktop_wake/profiles.py b/src/jiangchang_desktop_wake/profiles.py index dd4144c..f90ac09 100644 --- a/src/jiangchang_desktop_wake/profiles.py +++ b/src/jiangchang_desktop_wake/profiles.py @@ -44,14 +44,12 @@ _BUILTIN = { }, "wecom_search": { "id": "wecom_search", - "description": "企业微信搜索结果浮层 SearchResultWindow2(n2-wake 对浮层子类化,1.2.8+)", + "description": "企业微信搜索结果浮层 SearchResultWindow2;仅进程外软唤醒(禁止注入,1.2.9+)", "window_class": "SearchResultWindow2", "warm_min_findall": 20, "warm_markers": ["search_result", "user_item"], "activate_maximize": False, - "wake_strategies": ["activate", "native_helper"], - "native_helper": "desktop-wake.exe", - "native_args": ["wecom", "0x{hwnd_hex}", "--mode", "n2-wake"], + "wake_strategies": ["wm_getobject", "msaa_from_window"], }, } diff --git a/src/jiangchang_desktop_wake/profiles/wecom_search.json b/src/jiangchang_desktop_wake/profiles/wecom_search.json index 35dd482..0bbb1b2 100644 --- a/src/jiangchang_desktop_wake/profiles/wecom_search.json +++ b/src/jiangchang_desktop_wake/profiles/wecom_search.json @@ -1,14 +1,12 @@ { "id": "wecom_search", - "description": "企业微信搜索结果浮层 SearchResultWindow2(联系人列表 user_item);n2-wake 对浮层本身子类化(1.2.8+ 支持多窗),禁止 Maximize", + "description": "企业微信搜索结果浮层 SearchResultWindow2;仅进程外软唤醒(禁止 n2-wake/注入,1.2.9+)", "window_class": "SearchResultWindow2", "warm_min_findall": 20, "warm_markers": ["search_result", "user_item"], "activate_maximize": false, "wake_strategies": [ - "activate", - "native_helper" - ], - "native_helper": "desktop-wake.exe", - "native_args": ["wecom", "0x{hwnd_hex}", "--mode", "n2-wake"] + "wm_getobject", + "msaa_from_window" + ] } diff --git a/src/jiangchang_desktop_wake/types.py b/src/jiangchang_desktop_wake/types.py index 3b2b306..69fbd5e 100644 --- a/src/jiangchang_desktop_wake/types.py +++ b/src/jiangchang_desktop_wake/types.py @@ -30,7 +30,7 @@ class Profile: # 浮层窗(如搜索结果)禁止 Maximize,避免打乱搜索态 activate_maximize: bool = True # native_helper / n2-wake 目标窗 class;空则用 probe 窗本身。 - # 1.2.8+ wecom_search 留空(对浮层子类化);若需改注其它窗可再设。 + # wecom_search 留空;1.2.9+ 浮层禁止 native 注入,勿再设为浮层自身。 native_hwnd_class: Optional[str] = None @classmethod diff --git a/tests/desktop_wake/test_profiles_and_types.py b/tests/desktop_wake/test_profiles_and_types.py index 794a09d..123edbe 100644 --- a/tests/desktop_wake/test_profiles_and_types.py +++ b/tests/desktop_wake/test_profiles_and_types.py @@ -34,10 +34,11 @@ class ProfileTests(unittest.TestCase): self.assertIn("search_result", p.warm_markers) self.assertIn("user_item", p.warm_markers) self.assertFalse(p.activate_maximize) - # 1.2.8+:对浮层本身 n2-wake(多窗子类化);不再改注主窗 + # 1.2.9+:浮层禁止 n2-wake/注入(会崩企微);仅进程外软唤醒 self.assertFalse((p.native_hwnd_class or "").strip()) - self.assertIn("native_helper", p.wake_strategies) - self.assertIn("n2-wake", p.native_args or []) + self.assertNotIn("native_helper", p.wake_strategies) + self.assertIn("wm_getobject", p.wake_strategies) + self.assertIn("msaa_from_window", p.wake_strategies) def test_resolve_native_hwnd_fallback_without_class(self): from jiangchang_desktop_wake.wake import resolve_native_hwnd