Write Windows dynamic wallpaper [Revision 1] (based on Windows10 desktop)

1, Miscellaneous words
As a programming enthusiast, you may not be too optimistic about the standard static desktop. You may be amazed at the effect of WallpaperEngine. You may also want to make a personalized desktop beautification program. First, you should start with dynamic wallpaper. It was originally studied from the existing program, but the effect was not ideal, because I still didn't understand how to set the dynamic wallpaper through the code (as you can see, I grabbed the dynamic wallpaper program of KuGou cool dog music. I felt that this was the standard template, but it had a flaw: there was no sound. The program can be in Here (extraction code: 6cth) So I saw the articles of several bloggers Principles of Windows dynamic desktop,<Write Windows dynamic wallpaper (about Windows 10 desktop) (I) >, benefit a lot.

Effect drawing of standard template  

As for why I want to write this article, this is because the author mentioned the problem of code in both articles: after the task window pops up, the wallpaper program window highlights (forced rendering after the parent window relationship changes), and I network information (qq_40036189) has replied to a solution. Although this solution is not perfect, I hope more scholars can see it and make some suggestions.

2, Topic

Desktop WorkerW group displayed by Spy + +  

In this figure, SysListView32 and SHELLDLL_DefView is used to manage and display desktop icons. Progman is the total desktop displayed (especially the initial state), SHELLDLL_DefView is responsible for displaying desktop icons.

As for SysListView32, this window controls the list of desktop icons. If you want to add the desktop sorting function, you may need to use it.

As mentioned at the beginning, as long as you open the task window, SysListView32 and shelldll will appear_ The defview window is separated from the Progman window and produces an independent WorkerW space. The reason is that the system sends a group of messages to Progman, which causes it to abandon the original relationship state. This is the additional Buff effect of WorkerW smooth transition processing.

In fact, this way, we can still use the features of win10 desktop to achieve our goal. But it will have an impact (if you don't do anything in the original way).

About WorkerW, I also observed it in the Explorer window (file explorer), so there are usually multiple, depending on how many folders you open. We should pay attention to the total WorkerW of the desktop (let's call it so).

3, Write code
Progman displays wallpaper. We just need to put our own window in front of it, that is, set our own window as a child window of progman. Note that the WorkerW window without child window is hidden. It is responsible for smooth transition, that is, it is drawn in front of progman. We must hide it in order to display our window normally.
So an early generation of code appeared:

/*
Copyright notice: This is the original code of CSDN blogger "network information", which follows CC 4.0 BY-SA copyright agreement. Please attach the original source link and this notice for reprint.
Original link: https://blog.csdn.net/qq_40036189/article/details/108210747
*/

#include <iostream>
#include <windows.h>
#include <string>
 
//Desktop related window handle
HWND SysListView32 = nullptr;
HWND SHELLDLL_DefView = nullptr;
HWND WorkerW = nullptr;
HWND Program = nullptr;
 
//Report error
void Error(const char* _error,const char* _from)
{
    std::cout<<"Error:"<<_error<<" From:"<<_from<<std::endl;
}
 
//EnumWindows callback function
inline BOOL CALLBACK EnumWindowsProc(_In_ HWND TopHandle, _In_ LPARAM topparamhandle)
{
    char str[256];
    GetClassName(TopHandle,str,256);
    std::string s1 = str;//Check whether the window is called WorkerW
    if(s1 == "WorkerW")
    {
        //Check whether there is a child window SHELLDLL_DefView
        HWND def = FindWindowEx(TopHandle,nullptr,"SHELLDLL_DefView",nullptr);
        if(nullptr != def)
        {
            WorkerW = TopHandle;
            SHELLDLL_DefView = def;
        }else
        {
            ShowWindow(TopHandle,SW_HIDE);
        }
 
    }
 
    return true;
}
 
 
//Set the desktop environment
//Get all relevant handles
bool SetDeskEnvironment()
{
    const char *ErrorF = "SetDeskEnvironment";
 
    //Get Program handle
    Program = FindWindow("Progman","Program Manager");
    if(nullptr == Program){
        Error("Can't Get Program Hwnd",ErrorF);
        return false;
    }
 
    //Send a message for Windows to generate a Worker
    SendMessageTimeout(Program, 0x052c, 0 ,0, SMTO_NORMAL, 0x3e8,nullptr);
 
    EnumWindows(EnumWindowsProc,(LPARAM)nullptr);
 
    //Get SysListView32 handle
    SysListView32 = FindWindowEx(SHELLDLL_DefView,nullptr,"SysListView32","FolderView");
    if(nullptr == SysListView32){
        Error("Can't Get SysListView32 Hwnd",ErrorF);
        return false;
    }
 
    return true;
}
 
//Set window as desktop background
bool SetWallWindow(LPCSTR ClassName,LPCSTR TitleName)
{
    const char *ErrorF = "SetWallWindow";
 
    //Find the handle of the window to be implanted into the desktop
    HWND hWall = FindWindow(ClassName,TitleName);
    if(nullptr == hWall){
        Error("Can't Get hWall Hwnd",ErrorF);
        return false;
    }
 
 
    if(nullptr == Program){
        Error("Not get Program Hwnd",ErrorF);
        return false;
    }
 
    //Make the window a child of Progman
    HWND hPro = SetParent(hWall,Program);
    if(nullptr == hPro){
        Error("Set Parent Failed",ErrorF);
        return false;
    }
 
    return true;
}
 
 
int main()
{
    std::cout<<"Program Start!"<<std::endl;
 
    SetDeskEnvironment();
 
    SetWallWindow(nullptr,"ACGLooking");
 
    std::cout<<"Press any key..."<<std::endl;
    getchar();
 
    return 0;
}

It seems that we have successfully inserted our sub window:

  However, as soon as you open the task window: press the key combination "Win logo"+   Tab ", or press this button on the taskbar

The desktop changes:

        Once we close the window and return to the desktop again, we will find that the wallpaper window (I use a folder instead) is highlighted, and there will be problems with the rendering of the form: the form control will display the desktop background or something. This shows that our window is no longer a child window of Progman, but should now be shelldll_ The sub window of defview and above SysListView32, we were forced to get the special effect of desktop background preview (why do I want this).

  4, Improvement scheme

Then I carefully studied the trigger conditions. Figure 1 shows the trigger conditions before the task window is opened (normal) and Figure 2 shows the trigger conditions after the task window is opened (abnormal). The records were retrieved respectively with the window monitoring tool (using the Capture Wizard):

  Figure 1. Before opening the task window

Figure 2. After the task window is opened

  We observed that a series of wonderful changes will occur when activating the task window. For example, all sub windows under Progman follow SHELLDLL_DefView as the new parent window (I tried it manually with SetParent. As long as the parent window is set to it, the rendering of the child window will be wrong, and the preview of the original static wallpaper window will be displayed locally, which has the same effect as the magic event); The Progman window is separated separately, and this adjustment is not restored after closing the task window.

It turned out that our originator was in the father son relationship with Progman. So I thought of setting the wallpaper program window as Progman's parent window (this is generally not recommended, because as long as the wallpaper program terminates unexpectedly or the wallpaper window is destroyed, the icon on the desktop will disappear unless the shell Explorer is restarted or the icon window is reset), because the system will not quarrel with Progman's parent window (this is the case), I call this operation Progman reverse parenting.

 

And this method can ensure that our wallpaper window will not lose focus, so after adjustment, both windows can receive mouse and keyboard messages without tedious processing of Hook global messages.

Although reverse parent partially solves the problem of independent windows, the icon window will be blocked, and there are problems in message processing. Therefore, there will be no response.  

However, this operation will not solve the visualization problem of wallpaper. The original blogger has mentioned that SysListView32 will cover our window, because the default icon list is full screen on the desktop, and its window background will reflect the original static wallpaper window.

So, how should we solve it? Very simple, just use the algorithm to calculate the number of icons and the size of a single icon, and then calculate its actual area. Finally, we use   GetWindowRect and MoveWindow can reset the size of the list window.

The effect is shown in the figure:

Please look at the picture carefully.

The fact is not so simple * (we can see that the form of the icon list still displays the desktop shading image, and we can't see the later part). During the test, we found that the SysListView32 window has a black background. Under normal circumstances, the desktop image will also be displayed. As long as we minimize the [neither minimize nor hide, as will be mentioned later] Progman window, It will become a box with white words and black background. So a function SetLayeredWindowAttributes without complete documentation comes in handy.

I checked the prototype and usage of this function as follows:

SetLayeredWindowAttributes 
BOOL SetLayeredWindowAttributes(           
     HWND hwnd, 
     COLORREF crKey, 
     BYTE bAlpha, 
     DWORD dwFlags 
); 

hwnd is the handle to set the transparent form,  
crKey is the color value,  
Bhalpha is transparency, and the value range is [0255],  
dwFlags is transparent and can take two values:  
          When the value is LWA_ In alpha, the crKey parameter is invalid and the baalpha parameter is valid;  
          When the value is LWA_ When colorkey, the baalpha parameter is valid, and all places in the form where the color is crKey will become transparent.  
          LWA_ALPHA = 0x2 
          LWA_COLORKEY=0x1 

dwFlags include LWA_ALPHA and LWA_COLORKEY. LWA_ If alpha is set, the transparency is determined through bhalpha, LWA_ If colorkey is set, the transparent color is specified as crKey, and other colors are displayed normally.

Note: to make the form transparent, you must first have WS_EX_LAYERED extension attribute (this attribute was not defined in the old SDK, so it can be directly specified as 0x80000).  

According to my needs, I filter the background color to be transparent and keep the shading image (reflecting the desktop background color), so that the background picture is filtered and the white text can be displayed clearly. Of course, you can also make other changes.

    //Join WS_EX_LAYERED extended attribute
    SetWindowLong(workerw2, GWL_EXSTYLE,
        GetWindowLong(workerw2, GWL_EXSTYLE) ^ 0x80000);
    HINSTANCE hInst = LoadLibrary(L"User32.DLL");

    if (hInst)
    {

        typedef BOOL(WINAPI* MYFUNC)(HWND, COLORREF, BYTE, DWORD);
        MYFUNC fun = NULL;
        //Gets the pointer to the SetLayeredWindowAttributes function
        fun = (MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
        if (fun)fun(workerw2, 0, 128, 2);
        FreeLibrary(hInst);
    }
    SetLayeredWindowAttributes(workerw2, 0, 100, 2);//Set custom transparency effects,
                            //The first parameter passes in the SysListView32 window (icon list) handle

Debug again, and the effect is outstanding (the translucent color depends on the desktop background):

  When the article is written here, you may already think it is safe. However, things are by no means so simple.

At this point, when we started clicking on the icon list, an amazing scene appeared: our cursor turned into a large loading circle. Then the Explorer does not respond and triggers the default "automatic restart when Explorer does not respond", which is obviously caused by our operation.

Similarly, I found that once the task window is opened, the Explorer will not respond. Why is it so similar?

Our readers should still remember that I set Progman as the reverse parent before, that is, now the Progman window has become a child window. Our system will not find this window. This window disappears from the window list because it is directly SetParent as a child window (in fact, it is still there), so the system cannot find it, so the message sent cannot be processed, resulting in no response from the resource manager.

I saw a prompt about SetParent on MSDocs, which may be related to the error of form relationship setting:

For compatibility reasons, SetParent does not modify the WS of a window whose parent is being changed_ Child or WS_ Pop window style. Therefore, if hWndNewParent is NULL, you should also clear WS after calling SetParent_ Child bit and set WS_ Pop style. Conversely, if hWndNewParent is not NULL and the window was previously a child window of the desktop, WS should be cleared before calling SetParent_ Pop style and set WS_CHILD style.

When you change the parent of a window, you should synchronize the UISTATE of the two windows. For more information, see WM_CHANGEUISTATE and WM_UPDATEUISTATE.

If hWndNewParent and hWndChild are running in different DPI aware modes, unexpected behavior or errors may occur. The following table summarizes this behavior:

operationWindows 8.1Windows 10 (1607 and earlier)Windows 10 (1703 and later)

SetParent

(in process)

Not applicableforce reset  ( Of the current process)fail  ( ERROR_INVALID_STATE)

SetParent

(cross process)

force reset

 ( Process of child window)

force reset  ( Process of child window)force reset  ( Process of child window)

For more information about DPI awareness, see Windows high DPI documentation.

Moreover, Microsoft does not recommend setting the parent-child relationship between the windows of two different programs. Of course, I haven't experimented with this method yet. I adopted another method:

That is to forge a normal looking Progman window to process these messages. Since the incidental effect of the smooth transition of the previous task window is not very useful, at least the processing for Progman is invalid (it can not be processed), it is simple to directly receive the message and end it without doing anything? At first, I tried with the mentality of trying, but I didn't expect to succeed.

I create an independent Windows window in the console. Of course, the length and width of this window is not a big problem at all. You can set it to 0. However, the class name and title are very important and must be equal to the original Progman. You can know it through Spy + + and other tools. Class name: Progman; Title: Program Manager.

The method of making the initial Progman invisible mentioned earlier is similar to this. I set it as a window with length and width of 1 (minimization). Why not use ShowWindow (hWnd, SW)_ Hide) because you have to set its parent-child relationship later. After SW, it will be invalid.

At present, the complete code is as follows (in a hurry, relatively rough, don't read):

#include <iostream>
#include <windows.h>
#include <string>
#include <tchar.h>  

const static char* szClass = "Progman";
const static char* szTitle = "Program Manager";

//Window process
LRESULT CALLBACK __WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//Callback function
LRESULT CALLBACK __WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   //DC hdc;                // Device environment handle
   //AINTSTRUCT ps;         // Draw structure
   //ECT rect;               // Rectangular structure

    switch (msg)        //Processed messages
    {
    case WM_SIZE:
        break;
    case WM_CLOSE:
    case WM_PAINT:           //Process messages sent when the window area is invalid
       // hdc = BeginPaint(hwnd, &ps);
        //GetClientRect(hwnd, &rect);
       // DrawText(hdc, TEXT("Hello World"), -1, &rect, DT_ SINGLELINE | DT_ CENTER | DT_ VCENTER);  // written words
       // EndPaint(hwnd, &ps);
        break;
    case WM_DESTROY:         //Process messages when the window is closed
        MessageBox(hwnd, TEXT("close program!"), TEXT("end"), MB_OK | MB_ICONINFORMATION);
        PostQuitMessage(0);
        break;
    case 0x052c:
        return TRUE;
       
    default:
        break;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);        //DefWindowProc handles messages that are not handled by our custom message handling function
}


HWND workerw1 = NULL;     //Second WorkerW window handle
HWND workerw2 = NULL;     //
HWND workerw3 = NULL;     //
HWND SysListView32 = nullptr;
HWND SHELLDLL_DefView = nullptr;
LPARAM lparam;

inline BOOL CALLBACK EnumWindowsProc(HWND handle, LPARAM lparam)
{
    //Get the first WorkerW window
    HWND defview = FindWindowEx(handle, 0, L"SHELLDLL_DefView", NULL);

    if (defview != NULL) //Find the first WorkerW window
    {
        int nID = 0;
        HWND hwnd = ::GetDlgItem(defview, nID);
        printf("ChidID:%x\n",nID);
        //Gets the window handle of the second WorkerW window
       // workerw = FindWindowEx(0, handle, L"WorkerW", 0);
        workerw1 = FindWindowEx(0, handle, L"WorkerW", 0);
        workerw2 = FindWindowEx(defview, nullptr, L"SysListView32", L"FolderView");
        workerw3 = defview;
       // workerw1 = FindWindowEx(handle, 0, L"WorkerW", NULL);
    }
    return true;
}

//The parameter myAppHwnd is the window handle of the window program you developed
void SetDesktop(HWND myAppHwnd)
{
    int result;
    HWND windowHandle = FindWindow(L"Progman", NULL);
    SendMessageTimeout(windowHandle, 0x052c, 0, 0, SMTO_NORMAL, 0x3e8, (PDWORD_PTR)&result);
    windowHandle = FindWindow(L"Progman", NULL);
    //Enumeration window
   // HWND SysListView32 = FindWindowExA(SHELLDLL_DefView, nullptr, "SysListView32", "FolderView");
    EnumWindows(EnumWindowsProc, (LPARAM)NULL);

    //Hide the second WorkerW window. When Progman is the parent window, it needs to be hidden,
    //Otherwise, the program window will be overwritten by the second WorkerW
    RECT rect0;
    GetWindowRect(workerw1, &rect0);
    // resize window
    MoveWindow(workerw1, rect0.left, rect0.top, 150, 1050, true);
   // ShowWindow(workerw1, SW_HIDE);

    // Gets the original size of the window
    RECT rect;
    GetWindowRect(windowHandle, &rect);
    // resize window
    MoveWindow(windowHandle, rect.left, rect.top, 0, 0, true);
	// Progman reverse Parenting
    SetParent(windowHandle , myAppHwnd);
    // Gets the original size of the window
    RECT rect2;
    GetWindowRect(workerw2, &rect2);
    // resize window
    MoveWindow(workerw2, rect2.left, rect2.top, 150, 1050, true);
    //Join WS_EX_LAYERED extended attribute

    SetWindowLong(workerw2, GWL_EXSTYLE,
        GetWindowLong(workerw2, GWL_EXSTYLE) ^ 0x80000);
    HINSTANCE hInst = LoadLibrary(L"User32.DLL");

    if (hInst)
    {

        typedef BOOL(WINAPI* MYFUNC)(HWND, COLORREF, BYTE, DWORD);
        MYFUNC fun = NULL;
        //Gets the pointer to the SetLayeredWindowAttributes function
        fun = (MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
        if (fun)fun(workerw2, 0, 128, 2);
        FreeLibrary(hInst);
    }
    SetLayeredWindowAttributes(workerw2, 0, 100, 2);//Set transparency
    SetParent(workerw2, myAppHwnd);//Feasible defview2

    ::SetWindowPos(workerw3, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // Top window
    ::SetWindowPos(workerw2, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); // Top window
}


int main(int argc, char* argv[])
{
    //nMain();
    HINSTANCE hIns = ::GetModuleHandle(0);
    WNDCLASSEXA wc;
    wc.cbSize = sizeof(wc);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hIns;
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hIconSm = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.lpfnWndProc = __WndProc;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szClass;

    if (!RegisterClassExA(&wc)) exit(0);

    DWORD style = WS_OVERLAPPEDWINDOW;
    DWORD styleEx = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;

    //Calculate the window size of 800 wide and 600 high for the customer area
    RECT rect = { 0, 0, 800, 600 };
    AdjustWindowRectEx(&rect, style, false, styleEx);

    HWND hWnd = CreateWindowExA(styleEx, szClass, szTitle, style, 0, 0,
        rect.right - rect.left, rect.bottom - rect.top, 0, 0, hIns, 0);
    if (hWnd == 0) exit(0);

    UpdateWindow(hWnd);
    ShowWindow(hWnd, SW_SHOW);

    //TODO, init this

  /*  MSG msg = { 0 };
    while (msg.message != WM_QUIT) {
        if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else {
            continue;
              //TODO, do this
        }
    }*/
    std::cout << "Hello World!\n";

    HWND hWallre = FindWindowA(nullptr, "WebGLPA");
    SetDesktop(hWallre);
    getchar();
}

The running wallpaper startup program retrieves the player handle through the window title. The name used in the test is WebGLPA. The player needs to be full screen and borderless. After startup, manually open the task window once (trigger that event because I don't know what message he sent), and then the display and most operations will return to normal.

This is just a simple modification, and there are many things to deal with later:

1. The player cannot have an icon on the taskbar. For their own programs, this is easy to modify directly in the window;

2. In the example, the modification of icon bar width is static, which can be dynamically modified by calculating the number and size of icons;

3. The correct sub window list is not listed in the example, which can be improved by itself;

4. Forge Progman window to realize fully automatic inheritance of message processing;

5. This; The dynamic wallpaper shown in the example is only displayed in the current desktop view, and other views using Win10's multi desktop mode (virtual desktop) cannot be synchronized.

Let's write so much. If I have time, I may come to further modify it. Then the rest of the work will be handed over to all immortals!

Keywords: C++ Windows visualstudio

Added by canishk on Mon, 06 Sep 2021 00:17:02 +0300