Have you ever wondered how DWM Thumbnails were so buttery smooth on Windows 8 and newer when making animations/transitions (in the shell)?
No? Well, one thing is sure; they are not using public APIs like DwmRegisterThumbnail
and DwmUpdateThumbnailProperties
.
Let me present you…
*drum rolls*
…fresh new private APIs to get a shared Visual from any window of your choice.
Let’s get started!
Nota bene: Since symbols are stripped for these functions I had to guess the name and the type, which wasn’t that easy as it sounds.
Nota bene 2: Since Windows 10 build 20xxx (or maybe a bit earlier) there were some tiny changes in two functions.
What to include:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <Unknwn.h> #include <Windows.h> #include <ntstatus.h> #include <winternl.h> #include <wrl\implements.h> #include <comutil.h> #include <dcomp.h> #include <dwmapi.h> #include <dxgi1_3.h> #include <d3d11_2.h> #include <d2d1_2.h> #include <d2d1_2helper.h> //Either use this or add in your linker configuration #pragma comment(lib, "dxgi") #pragma comment(lib, "d3d11") #pragma comment(lib, "d2d1") #pragma comment(lib, "dcomp") #pragma comment(lib, "dwmapi") #pragma comment(lib, "ole32") |
Definitions:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
enum THUMBNAIL_TYPE { TT_DEFAULT = 0x0, TT_SNAPSHOT = 0x1, TT_ICONIC = 0x2, TT_BITMAPPENDING = 0x3, TT_BITMAP = 0x4 }; typedef HRESULT(WINAPI* DwmpCreateSharedThumbnailVisual)( IN HWND hwndDestination, IN HWND hwndSource, IN DWORD dwThumbnailFlags, IN DWM_THUMBNAIL_PROPERTIES* pThumbnailProperties, IN VOID* pDCompDevice, OUT VOID** ppVisual, OUT PHTHUMBNAIL phThumbnailId); typedef HRESULT(WINAPI* DwmpQueryWindowThumbnailSourceSize)( IN HWND hwndSource, IN BOOL fSourceClientAreaOnly, OUT SIZE* pSize); typedef HRESULT(WINAPI* DwmpQueryThumbnailType)( IN HTHUMBNAIL hThumbnailId, OUT THUMBNAIL_TYPE* thumbType); //pre-cobalt/pre-iron typedef HRESULT(WINAPI* DwmpCreateSharedVirtualDesktopVisual)( IN HWND hwndDestination, IN VOID* pDCompDevice, OUT VOID** ppVisual, OUT PHTHUMBNAIL phThumbnailId); //cobalt/iron (20xxx+) //No changes except for the function name. typedef HRESULT(WINAPI* DwmpCreateSharedMultiWindowVisual)( IN HWND hwndDestination, IN VOID* pDCompDevice, OUT VOID** ppVisual, OUT PHTHUMBNAIL phThumbnailId); //pre-cobalt/pre-iron typedef HRESULT(WINAPI* DwmpUpdateSharedVirtualDesktopVisual)( IN HTHUMBNAIL hThumbnailId, IN HWND* phwndsInclude, IN DWORD chwndsInclude, IN HWND* phwndsExclude, IN DWORD chwndsExclude, OUT RECT* prcSource, OUT SIZE* pDestinationSize); //cobalt/iron (20xxx+) //Change: function name + new DWORD parameter. //Pass "1" in dwFlags. Feel free to explore other flags. typedef HRESULT(WINAPI* DwmpUpdateSharedMultiWindowVisual)( IN HTHUMBNAIL hThumbnailId, IN HWND* phwndsInclude, IN DWORD chwndsInclude, IN HWND* phwndsExclude, IN DWORD chwndsExclude, OUT RECT* prcSource, OUT SIZE* pDestinationSize, IN DWORD dwFlags); //Custom names #define DWM_TNP_FREEZE 0x100000 #define DWM_TNP_ENABLE3D 0x4000000 #define DWM_TNP_DISABLE3D 0x8000000 #define DWM_TNP_FORCECVI 0x40000000 #define DWM_TNP_DISABLEFORCECVI 0x80000000 //We also need these if you want to use MultiWindow/VirtualDesktop. enum WINDOWCOMPOSITIONATTRIB { WCA_UNDEFINED = 0x0, WCA_NCRENDERING_ENABLED = 0x1, WCA_NCRENDERING_POLICY = 0x2, WCA_TRANSITIONS_FORCEDISABLED = 0x3, WCA_ALLOW_NCPAINT = 0x4, WCA_CAPTION_BUTTON_BOUNDS = 0x5, WCA_NONCLIENT_RTL_LAYOUT = 0x6, WCA_FORCE_ICONIC_REPRESENTATION = 0x7, WCA_EXTENDED_FRAME_BOUNDS = 0x8, WCA_HAS_ICONIC_BITMAP = 0x9, WCA_THEME_ATTRIBUTES = 0xA, WCA_NCRENDERING_EXILED = 0xB, WCA_NCADORNMENTINFO = 0xC, WCA_EXCLUDED_FROM_LIVEPREVIEW = 0xD, WCA_VIDEO_OVERLAY_ACTIVE = 0xE, WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 0xF, WCA_DISALLOW_PEEK = 0x10, WCA_CLOAK = 0x11, WCA_CLOAKED = 0x12, WCA_ACCENT_POLICY = 0x13, WCA_FREEZE_REPRESENTATION = 0x14, WCA_EVER_UNCLOAKED = 0x15, WCA_VISUAL_OWNER = 0x16, WCA_HOLOGRAPHIC = 0x17, WCA_EXCLUDED_FROM_DDA = 0x18, WCA_PASSIVEUPDATEMODE = 0x19, WCA_LAST = 0x1A, }; typedef struct WINDOWCOMPOSITIONATTRIBDATA { WINDOWCOMPOSITIONATTRIB Attrib; void* pvData; DWORD cbData; }; typedef BOOL(WINAPI* SetWindowCompositionAttribute)( IN HWND hwnd, IN WINDOWCOMPOSITIONATTRIBDATA* pwcad); |
How to get these functions:
These DLL exports don’t have function names, so you have to GetProcAddress with ordinal instead of the function name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
DwmpQueryThumbnailType lDwmpQueryThumbnailType; DwmpCreateSharedThumbnailVisual lDwmpCreateSharedThumbnailVisual; DwmpQueryWindowThumbnailSourceSize lDwmpQueryWindowThumbnailSourceSize; //PRE-IRON DwmpCreateSharedVirtualDesktopVisual lDwmpCreateSharedVirtualDesktopVisual; DwmpUpdateSharedVirtualDesktopVisual lDwmpUpdateSharedVirtualDesktopVisual; //20xxx+ DwmpCreateSharedMultiWindowVisual lDwmpCreateSharedMultiWindowVisual; DwmpUpdateSharedMultiWindowVisual lDwmpUpdateSharedMultiWindowVisual; bool InitPrivateDwmAPIs() { auto dwmapiLib = LoadLibrary(L"dwmapi.dll"); if (!dwmapiLib) return false; lDwmpQueryThumbnailType = (DwmpQueryThumbnailType)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(114)); lDwmpCreateSharedThumbnailVisual = (DwmpCreateSharedThumbnailVisual)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(147)); lDwmpQueryWindowThumbnailSourceSize = (DwmpQueryWindowThumbnailSourceSize)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(162)); //PRE-IRON lDwmpCreateSharedVirtualDesktopVisual = (DwmpCreateSharedVirtualDesktopVisual)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(163)); lDwmpUpdateSharedVirtualDesktopVisual = (DwmpUpdateSharedVirtualDesktopVisual)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(164)); //20xxx+ lDwmpCreateSharedMultiWindowVisual = (DwmpCreateSharedMultiWindowVisual)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(163)); lDwmpUpdateSharedMultiWindowVisual = (DwmpUpdateSharedMultiWindowVisual)GetProcAddress(dwmapiLib, MAKEINTRESOURCEA(164)); if (/* Check if is not zero, otherwise return false */) return false; return true; } //------------------ SetWindowCompositionAttribute lSetWindowCompositionAttribute; bool InitPrivateUser32APIs() { auto user32Lib = LoadLibrary(L"user32.dll"); if (!user32Lib) return false; lSetWindowCompositionAttribute = (SetWindowCompositionAttribute)GetProcAddress(user32Lib, "SetWindowCompositionAttribute"); if (!lSetWindowCompositionAttribute) return false; return true; } |
Create a new DirectComposition Device:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
using namespace Microsoft::WRL; ComPtr<ID3D11Device> direct3dDevice; ComPtr<IDXGIDevice2> dxgiDevice; ComPtr<ID2D1Factory2> d2dFactory2; ComPtr<IDCompositionDesktopDevice> dcompDevice; bool CreateDevice() { if (D3D11CreateDevice(0, //Adapter D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0, D3D11_SDK_VERSION, direct3dDevice.GetAddressOf(), nullptr, nullptr ) != S_OK) { //Maybe try creating with D3D_DRIVER_TYPE_WARP before returning false. //Always listen to device changes. return false; } if (direct3dDevice->QueryInterface(dxgiDevice.GetAddressOf()) != S_OK) { return false; } if (D2D1CreateFactory( D2D1_FACTORY_TYPE::D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory2), (void**)d2dFactory2.GetAddressOf()) != S_OK) { return false; } if (DCompositionCreateDevice3( dxgiDevice.Get(), __uuidof(dcompDevice), (void**)dcompDevice.GetAddressOf()) != S_OK) { return false; } return true; } |
Create a Window:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { //Your windows handling logic here. switch (message) { case WM_CLOSE: PostQuitMessage(0); break; default: break; } return DefWindowProc(hwnd, message, wParam, lParam); } HWND CreateWindowInternal(LPCWSTR lptitleName, LPCWSTR lpclassName, DWORD dwExStyle, DWORD dwStyle) { HINSTANCE hInstance = GetModuleHandle(NULL); WNDCLASS wc = { }; wc.lpfnWndProc = MyWndProc; wc.hInstance = hInstance; wc.cbWndExtra = 0; wc.lpszClassName = lpclassName; if (!RegisterClass(&wc)) return nullptr; const auto hwndParent = CreateWindowEx(dwExStyle, lpclassName, lptitleName, dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, 1000, 640, nullptr, nullptr, hInstance, NULL); //This part is needed ONLY if you plan on using MultiWindow/VirtualDesktop functions. BOOL enable = TRUE; WINDOWCOMPOSITIONATTRIBDATA wData{}; wData.Attrib = WCA_EXCLUDED_FROM_LIVEPREVIEW; wData.pvData = &enable; wData.cbData = sizeof(BOOL); lSetWindowCompositionAttribute(hwndParent, &wData); if (!hwndParent) return nullptr; return hwndParent; } |
Gluing it all together
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) { if (!InitPrivateDwmAPIs() || !InitPrivateUser32APIs() || !CreateDevice()) return 0; auto myWnd = CreateWindowInternal(L"Noice window", L"DwmThumbExampleClass", WS_EX_NOREDIRECTIONBITMAP, NULL); ShowWindow(myWnd, SW_SHOW); // ---- // PLACEHOLDER FOR OUR DWM THUMBNAIL FUNCTION CALLS! // ---- MSG msg{}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } } |
Create a window thumbnail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
//A window target of your choice HWND targetWindow = (HWND)FindWindow(L"Shell_TrayWnd", NULL); //Query the exact thumbnail source size SIZE windowSize{}; lDwmpQueryWindowThumbnailSourceSize(targetWindow, FALSE, &windowSize); //Pretty explanatory and also documented by Microsoft DWM_THUMBNAIL_PROPERTIES thumb{}; thumb.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_RECTSOURCE | DWM_TNP_OPACITY | DWM_TNP_ENABLE3D; thumb.opacity = 255; thumb.fVisible = TRUE; thumb.fSourceClientAreaOnly = FALSE; thumb.rcDestination = RECT{ 0, 0, windowSize.cx, windowSize.cy }; thumb.rcSource = RECT{ 0, 0, windowSize.cx, windowSize.cy }; HTHUMBNAIL hThumbWindow; ComPtr<IDCompositionVisual2> windowVisual; //Create the shared visual! auto res = lDwmpCreateSharedThumbnailVisual(myWnd, targetWindow, 2, &thumb, dcompDevice.Get(), (void**)windowVisual.GetAddressOf(), &hThumbWindow); //Create the target for the window. ComPtr<IDCompositionTarget> dcompTarget; if (dcompDevice->CreateTargetForHwnd(myWnd, FALSE, dcompTarget.GetAddressOf()) != S_OK) return 0; //You can either set the virtual desktop visual as root or create a new visual and set the content. if (dcompTarget->SetRoot(windowVisual.Get()) != S_OK) return 0; //Remember to commit. if (dcompDevice->Commit() != S_OK) return 0; //Once done, remember to release the Visual and not the hthumbnail. |
Create a MultiWindow/VirtualDesktop thumbnail
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
ComPtr<IDCompositionVisual2> virtualDesktopVisual; HTHUMBNAIL hThumbVirtualDesktop; auto virtualDeskRes = lDwmpCreateSharedMultiWindowVisual(myWnd, dcompDevice.Get(), (void**)virtualDesktopVisual.GetAddressOf(), &hThumbVirtualDesktop); auto monitorSize = RECT{ 0, 0, 1920, 1080 }; auto targetSize = SIZE{ 960, 540 }; HWND hwndTest = (HWND)0x1; //Exclude from the list what you want to exclude HWND* excludeArray = new HWND[1]; excludeArray[0] = hwndTest; //The include list is useless as it will include every window in every case. //You can only play with the exclusion list. auto virtualDesktopUpdate = lDwmpUpdateSharedMultiWindowVisual(hThumbVirtualDesktop, NULL, 0, excludeArray, 1, &monitorSize, &targetSize, 1); //Last parameter has to be "1" //Use lDwmpUpdateSharedVirtualDesktopVisual for 19043 or older //Create the target for the window. ComPtr<IDCompositionTarget> dcompTarget; if (dcompDevice->CreateTargetForHwnd(myWnd, FALSE, dcompTarget.GetAddressOf()) != S_OK) return 0; //You can either set the virtual desktop visual as root or create a new visual and set the content. if (dcompTarget->SetRoot(virtualDesktopVisual.Get()) != S_OK) return 0; //Remember to commit. if (dcompDevice->Commit() != S_OK) return 0; //Once done, remember to release the Visual and not the hthumbnail. |
You can use DwmUpdateThumbnailProperties
to update thumbnail properties. For anything else, you can rely on IDCompositionDevice/Visual
and make great animations. 🙂
Full demo code: DWM Thumbnail/VirtualDesktop IDCompositionVisual example (github.com)
Also, something about IDCompositionDevice & Windows.UI.Composition.Compositor interop coming SOONâ„¢
Hey, Thank you for the post, i have been searching for this for a long time, can i get a contact email of you, i have been trying to replicate the acrylic blur effect, i think it is possible using this method to get the host backdrop brush (the content behind my window). Please leave me a message back.
Hi!
Dont You know how to enable thumbnail live preview of minimized window, like it does wmp?
May be this parameter helps to do it WCA_VIDEO_OVERLAY_ACTIVE?
how can I get other virtual desktop’s thumbnails? thanks for the content btw