關(guān)鍵字:
capture minimized window
window thumbnail
IsIconic?
==================== 問題背景 ======================
最小化的窗口,API GetClientRect 返回的窗口尺寸是0x0,故無法通過GetDC+BitBlt捕獲到窗口畫面。
但是 Agora/zoom/tencentMeeting 都可以拿到最小化窗口的縮略圖。經(jīng)確認(rèn)這個(gè)程序并沒有注入任何dll到目標(biāo)窗口,且也沒有臨時(shí)顯示最小化了的目標(biāo)窗口。
如果用SHOW_RESTORE恢復(fù)最小化了的目標(biāo)窗口,目標(biāo)窗口是會(huì)收到WM_MOVE消息的,測(cè)試Agora/zoom/tencent抓最小化窗口的縮略圖時(shí),最小化的窗口并沒有收到這個(gè)消息,事實(shí)上,目標(biāo)窗口沒有收到任何窗口消息。
小記:
- agora zoom 騰訊會(huì)議 在目標(biāo)窗口最小化時(shí), 都可以拿到窗口縮略圖
- 拿到的該縮略圖,畫面和任務(wù)欄鼠標(biāo)懸停時(shí)顯示的縮略圖一致(該縮略圖并不是實(shí)時(shí)的,而是窗口最小化時(shí)刻的畫面)
- 獲取縮略圖時(shí),目標(biāo)窗口沒有收到任何窗口消息(SPY)
- 開始捕獲窗口后,這幾個(gè)視頻會(huì)議軟件 均可以恢復(fù)最小化了的窗口(即使目標(biāo)窗口是管理員權(quán)限啟動(dòng))
==================== 可用的方法 ====================
方法1(不推薦!)
WS_EX_LAYERED, SetLayeredWindowAttributes
方法2:
DWM接口,關(guān)鍵字:DwmRegisterThumbnail,DwmUpdateThumbnailProperties
通過注入Dll + hook API(DwmRegisterThumbnail)的方式驗(yàn)證zoom進(jìn)程,發(fā)現(xiàn)其就是用dwm捕獲最小化窗口畫面的。
在zoom的窗口選擇界面,所有目標(biāo)窗口的畫面捕獲和刷新 都是用的dwm,zoom每次調(diào)用dwmRegister 傳入的dest HWND都是同一個(gè)句柄,且剛剛就是窗口選擇界面的頂層窗口句柄,
目標(biāo)窗口最小化和restore期間,zoom沒有調(diào)用過dwmUnregister,應(yīng)該是調(diào)用了dwmUpdate(尚未驗(yàn)證)。
方法3:
WGC接口,關(guān)鍵字:GraphicsCapturePicker.PickSingleItemAsync (是系統(tǒng)提供的子進(jìn)程界面)
GitHub - robmikh/Win32CaptureSample: A simple sample using the Windows.Graphics.Capture APIs in a Win32 application.
================ DWM接口 ========================
關(guān)鍵字:
DWM(桌面窗口管理器)API , 可以實(shí)現(xiàn)獲取目標(biāo)窗口的縮略圖(即使目標(biāo)窗口是最小化)
DWM API應(yīng)用之縮略圖_老狼主的博客-CSDN博客_c++ dwm api
DWM API應(yīng)用之縮略圖 - CodeAntenna
Windows 使用 DuiLib 顯示屏幕和窗口縮略圖_12194415的技術(shù)博客_51CTO博客
DwmRegisterThumbnail function (dwmapi.h) - Win32 apps | Microsoft Learn
DwmUpdateThumbnailProperties function (dwmapi.h) - Win32 apps | Microsoft Learn
DwmUnregisterThumbnail function (dwmapi.h) - Win32 apps | Microsoft Learn
注意:
1. 一旦用DwmRegisterThumbnail和DwmUpdateThumbnail,綁定了縮略圖關(guān)系后,只要不調(diào)用DwmUnregister,源窗口的畫面就會(huì)被系統(tǒng)持續(xù)自動(dòng)更新到目標(biāo)窗口,不需要手動(dòng)去刷新畫面。
2. 一旦源窗口銷毀了,目標(biāo)窗口上的畫面也就沒有了(系統(tǒng)會(huì)自動(dòng)清空)
3. 源窗口和目標(biāo)窗口 都必須是頂層窗口,且目標(biāo)窗口必須是當(dāng)前進(jìn)程的窗口
4. UWP窗口的源窗口句柄,需要設(shè)置為parent host句柄(EnumWindows的回調(diào)形參HWND就是parent句柄)
#include <dwmapi.h>
#pragma comment(lib,"Dwmapi.lib")
HRESULT RegisterThumbWindow(HWND hWndSrc, HWND hWndDst)
{
HTHUMBNAIL thumbnail = NULL;
HRESULT hr = DwmRegisterThumbnail(hWndDst, hWndSrc, &thumbnail);
if (FAILED(hr))
return hr; // 如果窗口句柄不存在 或傳入了非頂層窗口的句柄 此處會(huì)出錯(cuò)
RECT dest;
GetClientRect(hWndDst, &dest);
DWM_THUMBNAIL_PROPERTIES dskThumbProps;
dskThumbProps.dwFlags = DWM_TNP_RECTDESTINATION | DWM_TNP_VISIBLE | DWM_TNP_SOURCECLIENTAREAONLY |DWM_TNP_OPACITY; // 標(biāo)識(shí)哪些字段已經(jīng)設(shè)置了有效值
dskThumbProps.fSourceClientAreaOnly = FALSE;
dskThumbProps.fVisible = TRUE;
dskThumbProps.rcDestination = dest;
dskThumbProps.opacity = 255;
hr = DwmUpdateThumbnailProperties(thumbnail, &dskThumbProps);
if (FAILED(hr))
OutputDebugStringA("error");
// DwmUnregisterThumbnail(thumbnail);
return hr;
}
void CMFCApplication4Dlg::OnBnClickedButton1()
{
// 要把縮略圖顯示在這個(gè)窗口 注意:根據(jù)MSDN這個(gè)窗口必須是頂層窗口,否則返回錯(cuò)誤E_INVALIDARG
// 注意 這個(gè)句柄 必須是【當(dāng)前進(jìn)程】的窗口句柄
HWND hwndDestination = m_hWnd;
// 想捕獲的源窗口 這個(gè)句柄也必須是頂層窗口句柄,UWP窗口在此處需要設(shè)置為parent host句柄
HWND hwndSource = (HWND)0X004E0286;
RegisterThumbWindow(hwndSource, hwndDestination);
}
===================== WGC接口 =====================
GitHub - walker-WSH/Win32CaptureSample: A simple sample using the Windows.Graphics.Capture APIs in a Win32 application.
這個(gè)項(xiàng)目,點(diǎn)擊按鈕“Open Picker”, 會(huì)彈出一個(gè)窗口 用來顯示所有窗口 包含最小化窗口的縮略圖。
但是這個(gè)彈出窗口是系統(tǒng)的,主程序退出后 這個(gè)窗口都還在。如果捕獲窗口利用的是WGC技術(shù),則可以完全復(fù)用系統(tǒng)的這個(gè)picker窗口。
這個(gè)彈出的子窗口,實(shí)際上是系統(tǒng)提供的獨(dú)立進(jìn)程:
================= WS_EX_LAYERED ===================
技術(shù)點(diǎn):臨時(shí)把窗口顯示出來(SW_RESTORE),捕獲到一幀窗口畫面后,再把窗口恢復(fù)最小化。
顯示窗口之前,需要做如下準(zhǔn)備:
1. 給目標(biāo)窗口增加擴(kuò)展屬性 WS_EX_LAYERED(增加了這個(gè)屬性 才可以設(shè)置窗口透明度)。
2. 用API設(shè)置窗口的透明度為透明 (讓用戶感知不到窗口被顯示了)SetLayeredWindowAttributes / GetLayeredWindowAttributes。
3. 臨時(shí)關(guān)閉系統(tǒng)的最大化和最小化的動(dòng)畫效果 animation(否則臨時(shí)restore最小化的窗口時(shí) 系統(tǒng)有動(dòng)畫效果 被用戶感知)。
恢復(fù)了最小化的窗口 抓取到窗口畫面后,再恢復(fù)以上幾個(gè)屬性(有些窗口可能之前就有透明度,要注意保存以前的值)
注意:
以上操作,如果是UWP窗口,需要設(shè)置的HWND對(duì)象是外層的host窗口。
TODO:
尚未驗(yàn)證過如果目標(biāo)窗口是管理員權(quán)限進(jìn)程的,如下API設(shè)置layered透明度是否還能成功?文章來源:http://www.zghlxwxcb.cn/news/detail-468864.html
HWND hWnd = 0;
if (pTemp->pObject->bIsUWP) {
hWnd = pTemp->pObject->hParent; // 如果是UWP窗口 需要使用host窗口
} else {
hWnd = pTemp->pObject->hActual;
}
if (IsWindow(hWnd)) {
if (IsIconic(hWnd)) {
ModifyStyleEx2(hWnd, 0, WS_EX_LAYERED); // 增加了這個(gè)屬性 才可以設(shè)置半透明
SetLayeredWindowAttributes(hWnd, 0, 155, LWA_ALPHA); // 設(shè)置整個(gè)窗口透明
// TODO: 臨時(shí)關(guān)閉系統(tǒng)的最大化和最小化的動(dòng)畫效果
ShowWindow(hWnd, SW_RESTORE); // 顯示窗口
// TODO: capture window video
// TODO: 移除layered屬性,恢復(fù)最小化狀態(tài)。
// 注意:需要確認(rèn)之前是否有l(wèi)ayer屬性,以及對(duì)應(yīng)透明度。GetLayeredWindowAttributes
ShowWindow(hWnd, SW_MINIMIZE);
}
}
備注:
顯示最小化窗口時(shí),如果目標(biāo)窗口是管理員權(quán)限運(yùn)行,而自己的進(jìn)程不是,則使用ShowWindow則會(huì)收到last error = 5。此時(shí)想顯示最小化的目標(biāo)窗口,應(yīng)該用如下兩種方法之一。文章來源地址http://www.zghlxwxcb.cn/news/detail-468864.html
// 方法1
// [此功能不適用于一般用途。它可能會(huì)在后續(xù)版本的 Windows 中更改或不可用。]
// 根據(jù)微軟注釋 這個(gè)API不建議使用了。
SwitchToThisWindow(hWnd, TRUE)?
// 方法2 (推薦使用)
PostMessage(hWnd, WM_SYSCOMMAND, (WPARAM)SC_RESTORE, 0)
到了這里,關(guān)于捕獲最小化窗口的縮略圖畫面的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!