C# writing Windows dynamic desktop software to realize desktop interaction function

Dreamscene 2 version 1.3 has been released and now supports mouse and desktop interaction. This function will not affect performance and basically does not occupy CPU. This function gives me a deeper understanding of Windows message mechanism. In this blog, I will introduce the implementation in detail.

Welcome to Star and Fork https://github.com/he55/DreamScene2

Implementation principle

Using WIN32 API SetWindowsHookEx Function Hook mouse and keyboard messages, in the hook processing function to capture mouse and keyboard messages and then call PostMessage Function to send a forward message to the dynamic desktop window.

Set mouse and keyboard hooks

The first parameter of the function is the Hook type. The Hook mouse message can be passed to WH_MOUSE_LL, Hook keyboard messages can be transmitted to WH_KEYBOARD_LL. The second parameter is the address of the custom Hook message processing function. The third parameter of the function is the module handle where the Hook function is located. When the Hook type is WH_MOUSE_LL or WH_KEYBOARD_LL, you can directly transfer the current module handle. The fourth parameter of the function is the thread Id. pass NULL to capture all messages.

Set the Hook code. Save the return value of SetWindowsHookEx function, which is required when unloading Hook

HHOOK g_hLowLevelMouseHook = NULL;
HHOOK g_hLowLevelKeyboardHook = NULL;

HMODULE hModule = GetModuleHandle(NULL);
g_hLowLevelMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, hModule, NULL);
g_hLowLevelKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hModule, NULL);

Uninstall Hook code

UnhookWindowsHookEx(g_hLowLevelMouseHook);
UnhookWindowsHookEx(g_hLowLevelKeyboardHook);

Write hook handling functions

WH_MOUSE_LL and wh_ KEYBOARD_ The signature of the hook processing function of ll is the same. The wParam parameter is a message type, and the lParam parameter is a pointer, which is related to the type of the hook function. When the hook type is wh_ MOUSE_ LParam parameter when ll is MSLLHOOKSTRUCT Structure pointer. When the hook type is wh_ KEYBOARD_ lParam parameter when ll is KBDLLHOOKSTRUCT Structure pointer.

Hook handler signature

LRESULT CALLBACK xxxProc(
  _In_ int    nCode,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);

Mouse hook handler

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

Process WM_LBUTTONDOWN mouse down message

The wParam parameter of the mouse hook processing function is the mouse message type. The lParam parameter needs to be converted into the pointer of msllhooktrue structure, the pt field of msllhooktrue structure and the coordinates of the mouse relative to the screen. To forward the mouse press message, you need to see WM_LBUTTONDOWN Definition of message: WM_ The wParam parameter of lbuttondown message is the status of the key, and the low byte of lParam parameter is the x coordinate of the cursor and the high byte is the y coordinate of the cursor. Note that the meanings of the wParam and lParam parameters of the mouse hook processing function and the PostMessage function are different, and they need to be converted into the parameters required by the PostMessage function.

WM_LBUTTONDOWN processing method

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
    LONG lp = MAKELONG(p->pt.x, p->pt.y); // Low byte x coordinate, high byte y coordinate

    if (wParam == WM_LBUTTONDOWN) {
        PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp); // Send mouse down message to dynamic desktop window
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

WM_LBUTTONUP and WM_MOUSEMOVE is handled in the same way

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
    LONG lp = MAKELONG(p->pt.x, p->pt.y);

    if (wParam == WM_MOUSEMOVE) {
        PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
    }
    else  if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONUP) {
        PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp);
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

Optimize mouse message forwarding

The above code will forward all mouse messages. In fact, it does not want to forward all mouse messages. For the messages of mouse press and release, only the mouse messages focusing on the desktop are forwarded.

Determine whether the foreground window is a desktop

BOOL DS2_IsDesktop(void) {
    HWND hProgman = FindWindow("Progman", "Program Manager");
    HWND hWorkerW = NULL;

    HWND   hShellViewWin = FindWindowEx(hProgman, NULL, "SHELLDLL_DefView", NULL);
    if (!hShellViewWin)
    {
        HWND hDesktopWnd = GetDesktopWindow();
        do
        {
            hWorkerW = FindWindowEx(hDesktopWnd, hWorkerW, "WorkerW", NULL);
            hShellViewWin = FindWindowEx(hWorkerW, NULL, "SHELLDLL_DefView", NULL);
        } while (!hShellViewWin && hWorkerW);
    }

    HWND hForegroundWindow = GetForegroundWindow();
    return hForegroundWindow == hWorkerW || hForegroundWindow == hProgman;
}

For the message of mouse movement, forward the message of mouse movement on the desktop.

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
    LONG lp = MAKELONG(p->pt.x, p->pt.y);

    if (wParam == WM_MOUSEMOVE) {
        RECT rect;
        GetWindowRect(GetForegroundWindow(), &rect);

        if (!PtInRect(&rect, p->pt)) {
            PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
        }
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

Complete mouse hook processing function code

LRESULT CALLBACK LowLevelMouseProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
    LONG lp = MAKELONG(p->pt.x, p->pt.y);

    if (DS2_IsDesktop()) {
        if (wParam == WM_MOUSEMOVE) {
            PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
        }
        else  if (wParam == WM_LBUTTONDOWN || wParam == WM_LBUTTONUP) {
            PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp);
        }
        else  if (wParam == WM_MOUSEWHEEL) {
            // TODO:
        }
    }
    else  if (wParam == WM_MOUSEMOVE) {
        RECT rect;
        GetWindowRect(GetForegroundWindow(), &rect);

        if (!PtInRect(&rect, p->pt)) {
            PostMessage(g_hWnd, (UINT)wParam, MK_XBUTTON1, lp);
        }
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

Keyboard hook handler

The wParam parameter of the keyboard hook processing function is the keyboard message type, and the lParam parameter needs to be converted into a KBDLLHOOKSTRUCT structure pointer. scanCode field and vkCode field are used in KBDLLHOOKSTRUCT structure. Keyboard message WM_KEYDOWN and WM_KEYUP The wParam parameter of the message is vkCode, and the meaning of the lParam parameter is complex.

WM_ lParam parameter bit description of Keydown message

Bits explain
0-15 The duplicate count of the current message.
16-23 Scan code
24 Indicates that the key is an extension key. If it is an extension key, the value is 1, otherwise it is 0.
25-28 Reserved, not used.
29 Context code. For WM_KEYDOWN message this value is always 0.
30 Previous key status. The value is 1 if the key is off before sending the message, and 0 if the key is started.
31 Transition state. For WM_KEYDOWN message this value is always 0.

WM_ lParam parameter bit description of Keyup message

Bits explain
0-15 The duplicate count of the current message. For WM_ For Keyup messages, the repeat count is always 1.
16-23 Scan code
24 Indicates that the key is an extension key. If it is an extension key, the value is 1, otherwise it is 0.
25-28 Reserved, not used.
29 Context code. For WM_ The value of the Keyup message is always 0.
30 Previous key status. For WM_ The value of the Keyup message is always 1.
31 Transition state. For WM_ The value of the Keyup message is always 1.

Complete keyboard hook processing function code

LRESULT CALLBACK LowLevelKeyboardProc(int    nCode, WPARAM wParam, LPARAM lParam) {
    if (DS2_IsDesktop()) {
        KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;

        if (wParam == WM_KEYDOWN) {
            int lp = 1 | (p->scanCode << 16) | (1 << 24) | (0 << 29) | (0 << 30) | (0 << 31);
            PostMessage(g_hWnd, (UINT)wParam, p->vkCode, lp);
        }
        else if (wParam == WM_KEYUP) {
            int lp = 1 | (p->scanCode << 16) | (1 << 24) | (0 << 29) | (1 << 30) | (1 << 31);
            PostMessage(g_hWnd, (UINT)wParam, p->vkCode, lp);
        }
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

All codes

https://github.com/he55/DreamScene2


How to use Kanban https://www.cnblogs.com/he55/p/15705047.html

Write at the end

The next step is to add ffmpeg video playback engine

Keywords: C# .NET WPF WinForms

Added by johne281 on Fri, 31 Dec 2021 02:05:03 +0200