DWM Thumbnails. But with IDCompositionVisual.

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:

#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:

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.

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:

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:

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

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

    //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

    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â„¢

Demo with animations!

3 thoughts on “DWM Thumbnails. But with IDCompositionVisual.”

  1. 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.

    Reply
  2. 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?

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Enable Notifications OK No thanks