Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Draw on Windows 10 wallpaper in C++

Is there a way to get an handle to Windows's wallpaper (behind icons) in C++ in order to draw on it? That would allow to make an active desktop (discontinued after Windows XP) equivalent, a Wallpaper Engine equivalent, or any other similar tool. (Temperature and resources usage monitoring on the wallpaper in my case).

Note: the handle returned by GetDesktopWindow() returns the window at desktop icons level, not behind it.

Solutions from similar questions aren't working for me. Specifically i tried VLC media player's wallpaper mode code.

Key code is:

hwnd = FindWindow( _T("Progman"), NULL );
 if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SHELLDLL_DefView"), NULL );
 if( hwnd ) hwnd = FindWindowEx( hwnd, NULL, _T("SysListView32"), NULL );
 if( !hwnd )
 {
     msg_Warn( p_vout, "couldn't find \"SysListView32\" window, "
               "wallpaper mode not supported" );
     return;
 }

But it will not draw on the wallpaper.

like image 201
Barnack Avatar asked May 14 '19 14:05

Barnack


1 Answers

Credits to this draw behind desktop icons C# page as reference. The article explains the theory behind the solution, which applies regardless of the programming language being used.

Long story short, the smooth fading animation you see on Windows 10 when changing wallpaper is achieved by creating a new window that does exactly what you're asking for, drawing under the icons. That window achieves the fade-in effect for the new wallpaper, and is created by the Program Manager.

In the mentioned article you can see together with C# implementation an explanation of every step. Here i'll write a C++ equivalent keeping comments from the source.

BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
    {
    HWND p = FindWindowEx(hwnd, NULL, L"SHELLDLL_DefView", NULL);
    HWND* ret = (HWND*)lParam;

    if (p)
        {
        // Gets the WorkerW Window after the current one.
        *ret = FindWindowEx(NULL, hwnd, L"WorkerW", NULL);
        }
    return true;
    }

HWND get_wallpaper_window()
    {
    // Fetch the Progman window
    HWND progman = FindWindow(L"ProgMan", NULL);
    // Send 0x052C to Progman. This message directs Progman to spawn a 
    // WorkerW behind the desktop icons. If it is already there, nothing 
    // happens.
    SendMessageTimeout(progman, 0x052C, 0, 0, SMTO_NORMAL, 1000, nullptr);
    // We enumerate all Windows, until we find one, that has the SHELLDLL_DefView 
    // as a child. 
    // If we found that window, we take its next sibling and assign it to workerw.
    HWND wallpaper_hwnd = nullptr;
    EnumWindows(EnumWindowsProc, (LPARAM)&wallpaper_hwnd);
    // Return the handle you're looking for.
    return wallpaper_hwnd;
    }

The C-like casts can be replaced with reinterpret_casts, according to your coding preferences.


One note that isn't mentioned in the article: Since when changing wallpaper a new WorkerW window is generated to achieve the fading effect, if the user tries to change wallpaper while your program is actively drawing and refreshing your instance of WorkerW, the user set background will be placed on top of your drawing, start fading in until it reaches 100% opacity, and lastly be destroyed, leaving your WorkerW still running.

like image 170
Barnack Avatar answered Oct 20 '22 16:10

Barnack