diff --git a/src/cad_mcp_plugins/mcp_plugins.cpp b/src/cad_mcp_plugins/mcp_plugins.cpp index 8425c96..e4e602b 100644 --- a/src/cad_mcp_plugins/mcp_plugins.cpp +++ b/src/cad_mcp_plugins/mcp_plugins.cpp @@ -66,6 +66,69 @@ namespace free(pImageCodecInfo); return -1; } + + // 判断窗口客户区中心点是否被其它窗口遮挡。 + // 原理:取客户区中心换算到屏幕坐标,用 WindowFromPoint 取该点最顶层窗口, + // 若其根窗口与目标窗口的根窗口不同,即视为被遮挡。 + bool IsWindowOccluded(HWND hwnd) + { + if (!hwnd || !::IsWindow(hwnd)) + return false; + + RECT rc; + if (!::GetClientRect(hwnd, &rc)) + return false; + + POINT pt; + pt.x = (rc.left + rc.right) / 2; + pt.y = (rc.top + rc.bottom) / 2; + ::ClientToScreen(hwnd, &pt); + + HWND hAtPoint = ::WindowFromPoint(pt); + if (!hAtPoint) + return false; + + return ::GetAncestor(hAtPoint, GA_ROOT) != ::GetAncestor(hwnd, GA_ROOT); + } + + // 把窗口强制置到前台并重绘。 + // SetForegroundWindow 在调用进程不是前台进程时会被系统忽略,故先用 + // AttachThreadInput 把当前线程与前台线程的输入队列挂接,再设置前台窗口。 + bool ForceWindowForeground(HWND hwnd) + { + if (!hwnd || !::IsWindow(hwnd)) + return false; + + if (::IsIconic(hwnd)) + ::ShowWindow(hwnd, SW_RESTORE); + if (!::IsWindowVisible(hwnd)) + ::ShowWindow(hwnd, SW_SHOW); + + if (::GetForegroundWindow() == hwnd) + return true; + + HWND hFore = ::GetForegroundWindow(); + DWORD foreThread = hFore ? ::GetWindowThreadProcessId(hFore, NULL) : 0; + DWORD thisThread = ::GetCurrentThreadId(); + + bool attached = false; + if (foreThread != 0 && foreThread != thisThread) + attached = (::AttachThreadInput(foreThread, thisThread, TRUE) != 0); + + ::SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + ::BringWindowToTop(hwnd); + ::SetForegroundWindow(hwnd); + + if (attached) + ::AttachThreadInput(foreThread, thisThread, FALSE); + + // 同步重绘,避免截到旧画面(RDW_UPDATENOW 会立即处理 WM_PAINT) + ::RedrawWindow(hwnd, NULL, NULL, + RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); + + return ::GetForegroundWindow() == hwnd; + } } class mcp_Tool_ViewportScreenshot: public mcp_Tool @@ -78,7 +141,18 @@ public: if (!hWnd) return mcp_Tool_Utility::make_error(-32000, L"无法获取CAD绘图区窗口句柄"); - // 2. 初始化 GDI+ + // 2. 确保窗口在前台:GDI 抓屏读的是屏幕内容,被其它窗口遮挡就会截到别的窗口。 + // 先尝试把 CAD 主窗口置前并重绘,再记录是否仍被遮挡。 + bool wasOccluded = IsWindowOccluded(hWnd); + if (wasOccluded || ::GetForegroundWindow() != ::GetAncestor(hWnd, GA_ROOT)) + { + HWND hMain = adsw_acadMainWnd(); + ForceWindowForeground(hMain ? hMain : hWnd); + ::Sleep(120); // 留一点时间给 DWM 合成,避免截到旧帧 + } + bool stillOccluded = IsWindowOccluded(hWnd); + + // 3. 初始化 GDI+ Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); @@ -86,7 +160,7 @@ public: std::string base64Result; bool success = false; - // 3. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期) + // 4. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期) { RECT rc; GetClientRect(hWnd, &rc); @@ -139,13 +213,23 @@ public: ReleaseDC(hWnd, hdcScreen); } - // 4. 关闭 GDI+ + // 5. 关闭 GDI+ Gdiplus::GdiplusShutdown(gdiplusToken); - // 5. 按照 MCP 多模态协议返回数据 + // 6. 按照 MCP 多模态协议返回数据 if (success) { - return mcp_Tool_Utility::make_mixed_result(L"已成功截取 AutoCAD 当前视口画面。", base64Result, "image/png"); + std::wstring msg = L"已成功截取 AutoCAD 当前视口画面。"; + if (stillOccluded) + { + // 窗口被遮挡时 GDI 抓屏会读到遮挡窗口的内容,截图不可靠,显式提示。 + msg += L"(警告:CAD 绘图区可能仍被其它窗口遮挡,截图内容可能不准确)"; + } + else if (wasOccluded) + { + msg += L"(已将 CAD 窗口置前,避免截到遮挡窗口)"; + } + return mcp_Tool_Utility::make_mixed_result(msg, base64Result, "image/png"); } else {