環(huán)境:msvc2017 , qt5.12.0 , Windows 10 (測試于版本號1809)

工具:qtcreat" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > 自定義·自制HTML壁紙

自定義·自制HTML壁紙

時間:2023-07-25 02:09:01 | 來源:網(wǎng)站運營

時間:2023-07-25 02:09:01 來源:網(wǎng)站運營

自定義·自制HTML壁紙:本文很大程度上基于 @今晚的風兒很喧囂 的文章 今晚的風兒很喧囂:手寫一個 windows 桌面動態(tài)壁紙(以下簡稱“參考文獻1”)

環(huán)境:msvc2017 , qt5.12.0 , Windows 10 (測試于版本號1809)

工具:qtcreator , Visual studio 2017 , spy++




好的,那么我們正式開始。

首先,如參考文獻1中所言,我們只需要向Progman推送一條消息WM_USER+300(快樂300)就可以使它創(chuàng)建兩個WorkerW,并把SHELLDLL_DefView轉(zhuǎn)移到第一個WorkerW中。

關(guān)于這一點,我在MSDN上并沒有找到相關(guān)說明,因此我無法確定這是一個會被長期維護的特性,但是到目前為止,它在我見到的大多數(shù)Windows10系統(tǒng)中運轉(zhuǎn)正常。

那么,理不直氣也不壯地,我們把它當成一個正確的東西來看待吧。

*hDesktop = FindWindowA("Progman", "Program Manager"); SendMessageA(*hDesktop, WM_USER + 300, 0, 0);啊,我這個笨蛋沒有做錯誤處理,大家不要學我。

(其實是因為如果出錯第一個函數(shù)會返回NULL,而第二個函數(shù)自然就是INVALID_HANDLE…)




好的,現(xiàn)在我們得到了一個Progman和兩個WorkerW

然后我們尋找包含SHELLDLL_DefView作為孩子的那個WorkerW,它就是我們的桌面圖標大雜燴。

然后…給它丟一個ShowWindow過去…看一眼效果圖…

就直接從樣例里找圖片了。

如果正常的桌面是這樣的

那么隱藏了這個WorkerW之后是這個樣子的




好的,那么我們現(xiàn)在取出這一個Progman,一個承載著圖標的WorkerW(以下簡稱WorkerW1),還有它身后的那個WorkerW(以下簡稱WorkerW2),裝盤(劃掉)待用。


然后,就是要“給自己一個明確的定位”了。

到底是作為Progman的獨生子女,WorkerW2的獨生子女,還是WorkerW1的若干子女中最靠后的一個?




嘛…我們先做一下實驗吧。

作為Progman的獨生子女出現(xiàn)時:

作為WorkerW2的獨生子女出現(xiàn)時:

(沒錯就是剛才那張)

(說到這里你們也知道我最終采取的是哪個方案了……)




作為WorkerW1的孩子時:

enmmmm……我有點懷疑窗口沒打開(這是電腦自帶壁紙)

看一眼…

好了,這個方案可以放棄了。

畢竟我還不想修改那幾個系統(tǒng)窗口的屬性,而很明顯地,在這個地方的時候它是顯示不出來的。




所以…我們在剩下的兩種方案之中挑選。那為什么不選擇Progman呢?

這里有一個小小的問題。

程序運行時,一切正常。

但是當我們用win+tab切換一下桌面之后…

我們驚奇地發(fā)現(xiàn),這個過程依然涉及到那一條神奇的消息:WM_USER+0x12C

于是,我們辛辛苦苦過繼給Progman的窗口,就被隨手丟到了WorkerW1的名下

驚不驚喜意不意外




所以我們最終的選擇是WorkerW2。當然,很多時候WorkerW2是隱藏且不接受消息的,所以我們要手動喚醒之。

SetParent(reinterpret_cast<HWND>(item->winId()),hDesktop); ShowWindow(hWorkerX,SW_SHOW); EnableWindow(hWorkerX,TRUE);然后,我們試圖截獲桌面上的鼠標按鍵。

這里有多種做法。比如:代碼注入/線程注入/線程鉤子/全局鉤子

代碼注入和線程注入的難度較大,先跳過。全局鉤子網(wǎng)上資料很多,跳過。

這里我們來試一試線程鉤子。我們掛鉤WH_MOUSE和WH_KEYBOARD。根據(jù)MSDN的說法,對其它線程掛鉤子,需要把函數(shù)放置于dll中。

所以,現(xiàn)在我們在dll中編寫如下函數(shù)。

LRESULT CALLBACK MouseProc(_In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam) { //according to MSDN, when nCode<0, do nothing if (nCode < 0)return CallNextHookEx(hHook, nCode, wParam, lParam); //when middle button is released, hide the desktop. if (wParam == WM_MBUTTONUP) { ShowWindow(WorkerW, SW_HIDE); return 1; } //passing mouse actions: it can not handle properly when you drag. if ((wParam == WM_LBUTTONDOWN)|| (wParam == WM_LBUTTONUP)|| (wParam == WM_LBUTTONDBLCLK)||(wParam==WM_MOUSEMOVE)) { MOUSEHOOKSTRUCT *in = reinterpret_cast<MOUSEHOOKSTRUCT*>(lParam); if(in->pt.x) PostMessage(Recv, MsgCode, wParam, MAKELPARAM(in->pt.x, in->pt.y)); } return CallNextHookEx(hHook, nCode, wParam, lParam);}大概的意思是,如果點擊鼠標中鍵,則隱藏桌面圖標。而對左鍵的動作,保留原有處理方式并轉(zhuǎn)發(fā)給我們的窗口。

需要注意的是,因為我們的dll中涉及到數(shù)據(jù)的跨進程共享,因此需要在dll中聲明共享數(shù)據(jù)段。

#pragma data_seg("Shared")......//待定義的變量們#pragma data_seg()然后,我們就可以使用SetWindowsHookEx進行掛鉤了。

需要注意的是,我只是為了一些強迫癥的原因?qū)⑦@一段代碼也置于dll中,實際上這一段代碼的位置可以是任意的。

BOOL StartHook(_In_ HWND pIcons, _In_ HWND pWorkerW, _In_ HWND pRecv) { //Hook into the window with icons. DWORD tid = GetWindowThreadProcessId(pIcons, NULL); if (tid == 0)return FALSE; hHook = SetWindowsHookEx(WH_MOUSE, MouseProc, Me, tid); if (hHook == 0)return FALSE; //make sure the icons are shown. ShowWindow(WorkerW, SW_SHOW); //Retrieve necessary data. Icons = pIcons; WorkerW = pWorkerW; Recv = pRecv; return TRUE;}void StopHook(){ //clear data up. Icons = 0; WorkerW = 0; Recv = 0; //make sure that the icons are shown ShowWindow(WorkerW, SW_SHOW); //release the hooks UnhookWindowsHookEx(hHook); hHook = 0;}好的。這樣子我們就成功地截獲了Windows鼠標消息。

嘛,需要注意的是,在桌面圖標被隱藏之后,鼠標輸入會直接作用在我們的窗體上,所以…我們需要在這里處理鼠標中鍵信息,來重新顯示桌面圖標。




然后再說一下關(guān)于函數(shù)的導入和導出吧…

正常情況下,c++語言中,用dllexport導出的函數(shù)的函數(shù)名是會帶有一串修飾字符串的…這給我們也帶來了一些不便。

所以我們采用比較古老的方式…寫一個def文件,來確保導出過程中函數(shù)名不變

LIBRARY OpenDesktopEXPORTS MouseProc StartHook StopHook FindWindows SECTIONS Shared READ WRITE SHARED然后,我們在使用時則需要通過LoadLibrary加載dll,之后利用GetProcAddress獲得函數(shù)地址。

大概是這樣的…

bool initDll(){ list.clear(); map.clear(); hDll=LoadLibraryA("OpenDesktop.dll"); if(hDll==nullptr)return false; FARPROC hFun1 = GetProcAddress(hDll, "StartHook"); FARPROC hFun2 = GetProcAddress(hDll, "FindWindows"); FARPROC hFun3 = GetProcAddress(hDll, "StopHook"); StartHook=reinterpret_cast<STARTHOOK>(hFun1); FindWindows=reinterpret_cast<FINDWINDOWS>(hFun2); StopHook=reinterpret_cast<STOPHOOK>(hFun3); if((StartHook==nullptr)||(FindWindows==nullptr)||(StopHook==nullptr))return false; return true; }其中幾個我們不認識的類型定義如下:

typedef BOOL (*STARTHOOK)(HWND,HWND,HWND);typedef BOOL (*FINDWINDOWS)(HWND*,HWND*,HWND*);typedef BOOL (*STOPHOOK)();之后就是直截了當?shù)漠斪髡:瘮?shù)那樣調(diào)用就好了啦。




接下來,還有一個小小的問題。

作為一個作死專業(yè)戶…我最常做的事情之一就是把explorer玩壞…然后…重新打開…

但是這里有一個尷尬的問題:當explorer死去,作為它的子窗口,我們的窗口…焉有完卵…

啊…如果你要檢測自己的窗口死沒死的話一定不要用Qt的isVisible什么的——窗口死去不算崩潰…

當然,寫析構(gòu)函數(shù)有時也會有些尷尬:如果沒有WM_CLOSE的話,不會觸發(fā)窗口類的析構(gòu)(天知道為啥我連WM_DESTROY都沒收到,第一句話就是WM_NCDESTROY)

目前看來…這種涉及到窗口崩潰之類的時候比較穩(wěn)妥的處理方案是IsWindow函數(shù)…然而微軟表示窗口句柄在釋放后是隨時可能循環(huán)利用的所以不能這么寫…




那怎么辦呢?

關(guān)鍵就在崩潰的是"explorer"這件事情上

我們再一次叫出我們的朋友spy++,來隨便監(jiān)視一個窗口。就拿這個網(wǎng)頁舉例子吧。

在我taskkill -f -im explorer.exe時它無動于衷。(廢話,死進程不會說話)

之后,當我啟動explorer時,它收到了一串消息。

這里我就取最后一個收到的消息TaskbarCreated來說吧。消息編碼如下:

UINT TaskbarCreated=RegisterWindowMessageA("TaskbarCreated");之后只要聆聽這個消息就好了啦…它會自動推送給所有有條件接收消息的頂級窗口的。

那么問題來了…我們的窗口崩潰之后,用什么接受啊…

只要在旁邊再放一個窗口不就好了啦…

實際上,大家完全可以放置一個透明的QWidget完成這個任務(wù)…




但是我上一篇文章結(jié)尾答應了要多一些Win32的玩法嘛…

所以這里就對不起各位看官看一段經(jīng)書了…

LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam ){ if(uMsg==TaskbarCreated)Explorer::init(); if(uMsg==CustomMessage){ //Building the mouse event to send. QPoint pt(LOWORD(lParam),HIWORD(lParam)); Explorer::DispatchMouseMessage(pt,wParam); }; return DefWindowProcA(hwnd,uMsg,wParam,lParam);}首先我們構(gòu)造這個窗口的窗口過程,并在其中處理消息。這里我們處理了Explorer重建的消息和自定義鼠標事件的消息。

接下來,就是如何把這個窗口丟在屏幕上的事情了。

首先我們先注冊一個窗體類DesktopMessageListener

WNDCLASS winclass; winclass.style=NULL; winclass.lpfnWndProc=WindowProc; winclass.cbClsExtra=NULL; winclass.cbWndExtra=NULL; winclass.hInstance=GetModuleHandleA(nullptr); winclass.hIcon=nullptr; winclass.hCursor=nullptr; winclass.hbrBackground=nullptr; winclass.lpszMenuName=nullptr; winclass.lpszClassName=L"DesktopMessageListener"; RegisterClass(&winclass);大概的意思就是…除了必要的參數(shù)之外,剩下的都一律寫0.

然后呢,我們把這樣的一個類實例化。

Listener=CreateWindowA( "DesktopMessageListener", nullptr, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, nullptr,nullptr,GetModuleHandleA(nullptr),nullptr); if(Listener==nullptr)return false;最后我們把它隱藏好

ShowWindow(Listener,SW_HIDE);這樣子,我們就完成了事件監(jiān)聽窗口的建立。




關(guān)于QWebEngine的種種坑,因為不涉及本文“自定義Windows”的主題,因此本來是不想討論的。

但是坑太多了不吐不快啊…




首先是向QWebEngineView推送鼠標消息。

sendEvent把消息送到QWebEngineView處,它毫無反應。

后來才知道,我們應該獲得QWebEngineView的子對象RenderWidgetHostViewQtDelegateWidget

關(guān)于其獲取,這里我采用了這個頁面中的方式:通過childPolished事件獲得指定孩子

當然,對這種方式不放心的諸位,可以參照下面這個頁面,枚舉孩子并發(fā)送消息。

在我的做法里,我們通過如下的方式獲得所需的子對象。

bool WebPage::event(QEvent *e){ //Events to a WebEngineView should be handled by a RenderWidgetHostViewQtDelegateWidget if (e->type() == QEvent::ChildPolished) { QChildEvent *child_ev = static_cast<QChildEvent*>(e); childObj = child_ev->child(); } return QWebEngineView::event(e);}而通過以下的方式將事件發(fā)送給孩子

void WebPage::SendToChild(QEvent *e){ QApplication::sendEvent(childObj,e);}最后的最后,加入防止重復運行的方式…

這里我們按照MS的推薦采用CreateMutex的方式。詳情請參照MSDN




好了,說了這么多…,上效果圖吧

https://www.zhihu.com/video/1027356941995130880然后,按照慣例,項目地址



關(guān)鍵詞:定義

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關(guān)閉