feat(plugins): 截图前检测 CAD 窗口遮挡并自动置前

get_viewport_screenshot 使用 GDI 抓屏(GetDC+BitBlt 读屏幕内容),
若 CAD 窗口被其它窗口遮挡会截到遮挡窗口。现增加:

- IsWindowOccluded:客户区中心点经 WindowFromPoint 取顶层窗口,
  根窗口不同即判定被遮挡。
- ForceWindowForeground:AttachThreadInput + SetWindowPos/BringWindowToTop/
  SetForegroundWindow 强制置前,RedrawWindow(RDW_UPDATENOW) 同步重绘。
- Execute 中先检测遮挡/前台状态,必要时置前并 Sleep(120) 等 DWM 合成,
  返回文本按「已置前」「仍被遮挡」给出提示。
This commit is contained in:
gjm committed 2026-10-13 23:55:40 +08:00
1 parent 1cbcbb92f4
commit 40aff77dd1
1 file changed
+89 -5
+89 -5
View File
@@ -66,6 +66,69 @@ namespace
free(pImageCodecInfo); free(pImageCodecInfo);
return -1; 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 class mcp_Tool_ViewportScreenshot: public mcp_Tool
@@ -78,7 +141,18 @@ public:
if (!hWnd) if (!hWnd)
return mcp_Tool_Utility::make_error(-32000, L"无法获取CAD绘图区窗口句柄"); 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; Gdiplus::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken; ULONG_PTR gdiplusToken;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
@@ -86,7 +160,7 @@ public:
std::string base64Result; std::string base64Result;
bool success = false; bool success = false;
// 3. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期) // 4. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期)
{ {
RECT rc; RECT rc;
GetClientRect(hWnd, &rc); GetClientRect(hWnd, &rc);
@@ -139,13 +213,23 @@ public:
ReleaseDC(hWnd, hdcScreen); ReleaseDC(hWnd, hdcScreen);
} }
// 4. 关闭 GDI+ // 5. 关闭 GDI+
Gdiplus::GdiplusShutdown(gdiplusToken); Gdiplus::GdiplusShutdown(gdiplusToken);
// 5. 按照 MCP 多模态协议返回数据 // 6. 按照 MCP 多模态协议返回数据
if (success) 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 else
{ {