pretty code

2021年6月24日 星期四

Hide/Show Desktop Icons on Windows 10 by C code

雖然我們在桌面按下滑鼠右鍵,就可以在檢視功能的子選單中選擇是否要顯示桌面圖示,但需使用滑鼠又要兩個操作步驟感覺就很不好用。

昨天晚上終於狠下心來把它完成,雖然我之前已經 Google 過了,但肩膀受傷後人就變得很懶,除了工作以外的程式都很懶得動工,這個星期老婆進公司上班故不用張羅三餐,我連自己的午餐都隨便吃,可能是偷懶的緣故,感覺這星期肩膀狀況還不錯,所以昨天才會想來做這件事。

好久沒寫 Windows 相關的程式了,很多 HWND 等的參數都變得很陌生。

一開始在沒 Google 前,直覺就是可能跟 registry 有關,也找到相關的 registry。

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced]
"HideIcons"=dword:00000001

原本以為只要修改這個值後,再想辦法用廣播方式送出 WM_SETTINGCHANGE 訊息,應該就可以生效,但據我實驗的結果,桌面程式知道我動了 registry,但並不會生效,我猜它應該只是讀 registry 得知目前的值,才決定是否要在 "顯示桌面圖示" 前打勾。

好吧,只好走 Google 來的方式,來解決這個問題。


要特別注意的一點,上面有說應該要看 OS 版本來決定對誰發送訊息,但我在寫 Code 前有先用 Spy 這類的軟體來找尋視窗,發現在 Windows 10 跟連結的說法不太一樣。

順序應該是如下:

01. 找到 Progman 這個 Window。
02. 找到 class 為 "SHELLDLL_DefView" 的視窗。
03. 送出 WM_COMMAND 訊息。


int toggleIconByShell() {
    int res = 0;

    HWND progman;
    HWND defView;

    // Find SHELLDLL_DefView class
    progman = FindWindowExA(NULL, NULL, "Progman", "Program Manager");
    printf("Program is 0x%p\n", progman);

    defView = FindWindowExA(progman, NULL, "SHELLDLL_DefView", "");
    printf("SHELLDLL_DefView is 0x%p\n", defView);


    // Send message to defView
    int lParam = 0;
    int wParam = 0x7402;

    LSTATUS lResult = SendMessage(defView, WM_COMMAND, wParam, lParam);

    if (lResult != ERROR_SUCCESS) {
        printErrorCode(lResult);
        res = lResult;
    }

    printf("OK\n");
    return res;
}

2021/06/24 更新

又把文章仔細看了一遍,SHELLDLL_DefView 在某些情況下,會從原本的 Progman 跑到 WorkerW 下面,故需要針對不同情況去找 Handle,將尋找 Handle 獨立出來變成函數,增加可讀性。


#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void printErrorCode(LSTATUS lResult)
{
    printf("ErrorCode = %d(0x%08X) \n", (int)lResult, (int)lResult);
}

BOOL EnumCallback(HWND hwnd, LPARAM lParam)
{
    char name[256];
    memset(name, 0x00, sizeof(name));

    GetClassNameA(hwnd, name, sizeof(name));

    if (strcmp(name, "WorkerW") == 0) {
        HWND *address = (HWND*)lParam;

        HWND defView = FindWindowExA(hwnd, NULL, "SHELLDLL_DefView", "");

        if (defView != NULL) {
            *address = defView;
            return FALSE;
        }
    }
    return TRUE;
}

HWND getDefViewHandle()
{
    HWND progman;
    HWND defView;

    // Find SHELLDLL_DefView class
    progman = FindWindowExA(NULL, NULL, "Progman", "Program Manager");
    printf("Program is 0x%p\n", progman);

    defView = FindWindowExA(progman, NULL, "SHELLDLL_DefView", "");
    printf("SHELLDLL_DefView is 0x%p\n", defView);

    if (defView == NULL) {
        HWND *address = &defView;
        // defView is moved to "WorkerW"
        EnumWindows(EnumCallback, (LPARAM)address);
        printf("After searching WorkerW, SHELLDLL_DefView is 0x%p\n", *address);
        defView = *address;

        // SHELLDLL_DefView is not the child of WorkerW.
        if (defView == NULL) {
            HWND desktop = GetShellWindow();
            defView = FindWindowExA(desktop, NULL, "SHELLDLL_DefView", "");
            printf("After searching desktop, SHELLDLL_DefView is 0x%p\n", defView);
        }
    }

    return defView;
}

int toggleIconByShell()
{
    int res = 0;

    HWND defView = getDefViewHandle();

    if (defView == NULL) {
        printf("SHELLDLL_DefView not found \n");
        return -1;
    }

    // Send message to defView
    int lParam = 0;
    int wParam = 0x7402;

    LSTATUS lResult = SendMessage(defView, WM_COMMAND, wParam, lParam);

    if (lResult != ERROR_SUCCESS) {
        printErrorCode(lResult);
        res = lResult;
    }

    printf("OK\n");
    return res;
}

int main(void) {
    toggleIconByShell();

    system("pause");
    return 0;
}

2021/06/25 更新

在已經知道訣竅的情況下,抓取訊息就變得很簡單,但當初怎樣找到標的物和要送的訊息,我想這才是最困難的部份吧。

沒有留言: