DreamScene2 1.3 版本已經發布了,現在支持滑鼠和桌面互動功能,這個功能不會影響性能,基本不占用 CPU,這個功能讓我對 Windows 訊息機制有了更深入的理解,在這篇博客中我會詳細介紹實作方式,

歡迎 Star 和 Fork https://github.com/he55/DreamScene2
實作原理
使用 WIN32 API SetWindowsHookEx 函式 Hook 滑鼠鍵盤訊息,在鉤子處理函式中處理捕獲滑鼠鍵盤訊息然后呼叫 PostMessage 函式向動態桌面視窗發送轉發訊息,
設定滑鼠和鍵盤鉤子
函式的第一個引數是鉤子型別,Hook 滑鼠訊息可以傳 WH_MOUSE_LL,Hook 鍵盤訊息可以傳 WH_KEYBOARD_LL,第二個引數是自定義的鉤子訊息處理函式地址,函式的第三個引數是鉤子函式所在的模塊句柄,當鉤子型別是 WH_MOUSE_LL 或者 WH_KEYBOARD_LL 時,可以直接傳當前模塊句柄,函式的第四個引數是執行緒 Id,傳 NULL 捕獲所有訊息,
設定 Hook 代碼,保存 SetWindowsHookEx 函式回傳值,卸載 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);
卸載 Hook 代碼
UnhookWindowsHookEx(g_hLowLevelMouseHook);
UnhookWindowsHookEx(g_hLowLevelKeyboardHook);
撰寫鉤子處理函式
WH_MOUSE_LL 和 WH_KEYBOARD_LL 的鉤子處理函式簽名相同,wParam 引數是訊息型別,lParam 引數是一個指標和鉤子函式的型別有關,當鉤子型別為 WH_MOUSE_LL 時 lParam 引數是 MSLLHOOKSTRUCT 結構體指標,當鉤子型別為 WH_KEYBOARD_LL 時 lParam 引數是 KBDLLHOOKSTRUCT 結構體指標,
鉤子處理函式簽名
LRESULT CALLBACK xxxProc(
_In_ int nCode,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
滑鼠鉤子處理函式
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
處理 WM_LBUTTONDOWN 滑鼠按下訊息
滑鼠鉤子處理函式的 wParam 引數就是滑鼠訊息型別,lParam 引數需要轉換成 MSLLHOOKSTRUCT 結構體指標,MSLLHOOKSTRUCT 結構體的 pt 欄位滑鼠相對于螢屏的坐標,想轉發滑鼠按下訊息,需要看 WM_LBUTTONDOWN 訊息的定義:WM_LBUTTONDOWN 訊息的 wParam 引數為按鍵的狀態,lParam 引數的低位元組為游標的 x 坐標、高位元組為游標的 y 坐標,需要注意滑鼠鉤子處理函式和 PostMessage 函式的 wParam 引數、lParam 引數含義不同,需要轉換成 PostMessage 函式需要的引數,
WM_LBUTTONDOWN 處理方法
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) {
MSLLHOOKSTRUCT* p = (MSLLHOOKSTRUCT*)lParam;
LONG lp = MAKELONG(p->pt.x, p->pt.y); // 低位元組 x 坐標、高位元組 y 坐標
if (wParam == WM_LBUTTONDOWN) {
PostMessage(g_hWnd, (UINT)wParam, MK_LBUTTON, lp); // 向動態桌面視窗發送滑鼠按下訊息
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
WM_LBUTTONUP 和 WM_MOUSEMOVE 處理方法一樣
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);
}
優化滑鼠訊息轉發
上面的代碼會轉發所有的滑鼠訊息,實際上并不想轉發所有的滑鼠訊息,對滑鼠按下和松開的訊息,只轉發焦點在桌面上的滑鼠訊息,
判斷前臺視窗是不是桌面
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;
}
對滑鼠移動的訊息,轉發滑鼠在桌面上的滑鼠移動訊息,
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);
}
完整的滑鼠鉤子處理函式代碼
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);
}
鍵盤鉤子處理函式
鍵盤鉤子處理函式的 wParam 引數就是鍵盤訊息型別,lParam 引數需要轉換成 KBDLLHOOKSTRUCT 結構體指標,KBDLLHOOKSTRUCT 結構體中用到的有 scanCode 欄位和 vkCode 欄位,鍵盤訊息 WM_KEYDOWN 和 WM_KEYUP 訊息的 wParam 引數為 vkCode,lParam 引數的含義比較復雜,
WM_KEYDOWN 訊息的 lParam 引數 bit 位說明
| Bits | 說明 |
|---|---|
| 0-15 | 當前訊息的重復計數, |
| 16-23 | 掃描代碼 |
| 24 | 指示該鍵是擴展鍵,如果它是擴展鍵則值為 1,否則為 0, |
| 25-28 | 保留,不使用, |
| 29 | 背景關系代碼,對于 WM_KEYDOWN 訊息該值始終為 0, |
| 30 | 之前的鍵狀態,如果在發送訊息之前鍵關閉則值為 1,如果鍵已啟動則值為 0, |
| 31 | 轉換狀態,對于 WM_KEYDOWN 訊息該值始終為 0, |
WM_KEYUP 訊息的 lParam 引數 bit 位說明
| Bits | 說明 |
|---|---|
| 0-15 | 當前訊息的重復計數,對于 WM_KEYUP 訊息,重復計數始終為1, |
| 16-23 | 掃描代碼 |
| 24 | 指示該鍵是擴展鍵,如果它是擴展鍵則值為 1,否則為 0, |
| 25-28 | 保留,不使用, |
| 29 | 背景關系代碼,對于 WM_KEYUP 訊息該值始終為 0, |
| 30 | 之前的鍵狀態,對于 WM_KEYUP 訊息該值始終為 1, |
| 31 | 轉換狀態,對于 WM_KEYUP 訊息該值始終為 1, |
完整的鍵盤鉤子處理函式代碼
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);
}
所有代碼
https://github.com/he55/DreamScene2

看板娘使用方法 https://www.cnblogs.com/he55/p/15705047.html
寫在最后
下一步會增加 ffmpeg 視頻播放引擎
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/396010.html
標籤:C#
