15158846557 在线咨询 在线咨询
15158846557 在线咨询
所在位置: 首页 > 营销资讯 > 网站运营 > 自定义·自制HTML壁纸

自定义·自制HTML壁纸

时间:2023-07-25 02:09:01 | 来源:网站运营

时间:2023-07-25 02:09:01 来源:网站运营

自定义·自制HTML壁纸:本文很大程度上基于 @今晚的风儿很喧嚣 的文章 今晚的风儿很喧嚣:手写一个 windows 桌面动态壁纸(以下简称“参考文献1”)

环境:msvc2017 , qt5.12.0 , Windows 10 (测试于版本号1809)

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




好的,那么我们正式开始。

首先,如参考文献1中所言,我们只需要向Progman推送一条消息WM_USER+300(快乐300)就可以使它创建两个WorkerW,并把SHELLDLL_DefView转移到第一个WorkerW中。

关于这一点,我在MSDN上并没有找到相关说明,因此我无法确定这是一个会被长期维护的特性,但是到目前为止,它在我见到的大多数Windows10系统中运转正常。

那么,理不直气也不壮地,我们把它当成一个正确的东西来看待吧。

*hDesktop = FindWindowA("Progman", "Program Manager"); SendMessageA(*hDesktop, WM_USER + 300, 0, 0);啊,我这个笨蛋没有做错误处理,大家不要学我。

(其实是因为如果出错第一个函数会返回NULL,而第二个函数自然就是INVALID_HANDLE…)




好的,现在我们得到了一个Progman和两个WorkerW

然后我们寻找包含SHELLDLL_DefView作为孩子的那个WorkerW,它就是我们的桌面图标大杂烩。

然后…给它丢一个ShowWindow过去…看一眼效果图…

就直接从样例里找图片了。

如果正常的桌面是这样的

那么隐藏了这个WorkerW之后是这个样子的




好的,那么我们现在取出这一个Progman,一个承载着图标的WorkerW(以下简称WorkerW1),还有它身后的那个WorkerW(以下简称WorkerW2),装盘(划掉)待用。


然后,就是要“给自己一个明确的定位”了。

到底是作为Progman的独生子女,WorkerW2的独生子女,还是WorkerW1的若干子女中最靠后的一个?




嘛…我们先做一下实验吧。

作为Progman的独生子女出现时:

作为WorkerW2的独生子女出现时:

(没错就是刚才那张)

(说到这里你们也知道我最终采取的是哪个方案了……)




作为WorkerW1的孩子时:

enmmmm……我有点怀疑窗口没打开(这是电脑自带壁纸)

看一眼…

好了,这个方案可以放弃了。

毕竟我还不想修改那几个系统窗口的属性,而很明显地,在这个地方的时候它是显示不出来的。




所以…我们在剩下的两种方案之中挑选。那为什么不选择Progman呢?

这里有一个小小的问题。

程序运行时,一切正常。

但是当我们用win+tab切换一下桌面之后…

我们惊奇地发现,这个过程依然涉及到那一条神奇的消息:WM_USER+0x12C

于是,我们辛辛苦苦过继给Progman的窗口,就被随手丢到了WorkerW1的名下

惊不惊喜意不意外




所以我们最终的选择是WorkerW2。当然,很多时候WorkerW2是隐藏且不接受消息的,所以我们要手动唤醒之。

SetParent(reinterpret_cast<HWND>(item->winId()),hDesktop); ShowWindow(hWorkerX,SW_SHOW); EnableWindow(hWorkerX,TRUE);然后,我们试图截获桌面上的鼠标按键。

这里有多种做法。比如:代码注入/线程注入/线程钩子/全局钩子

代码注入和线程注入的难度较大,先跳过。全局钩子网上资料很多,跳过。

这里我们来试一试线程钩子。我们挂钩WH_MOUSE和WH_KEYBOARD。根据MSDN的说法,对其它线程挂钩子,需要把函数放置于dll中。

所以,现在我们在dll中编写如下函数。

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);}大概的意思是,如果点击鼠标中键,则隐藏桌面图标。而对左键的动作,保留原有处理方式并转发给我们的窗口。

需要注意的是,因为我们的dll中涉及到数据的跨进程共享,因此需要在dll中声明共享数据段。

#pragma data_seg("Shared")......//待定义的变量们#pragma data_seg()然后,我们就可以使用SetWindowsHookEx进行挂钩了。

需要注意的是,我只是为了一些强迫症的原因将这一段代码也置于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鼠标消息。

嘛,需要注意的是,在桌面图标被隐藏之后,鼠标输入会直接作用在我们的窗体上,所以…我们需要在这里处理鼠标中键信息,来重新显示桌面图标。




然后再说一下关于函数的导入和导出吧…

正常情况下,c++语言中,用dllexport导出的函数的函数名是会带有一串修饰字符串的…这给我们也带来了一些不便。

所以我们采用比较古老的方式…写一个def文件,来确保导出过程中函数名不变

LIBRARY OpenDesktopEXPORTS MouseProc StartHook StopHook FindWindows SECTIONS Shared READ WRITE SHARED然后,我们在使用时则需要通过LoadLibrary加载dll,之后利用GetProcAddress获得函数地址。

大概是这样的…

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)();之后就是直截了当的当作正常函数那样调用就好了啦。




接下来,还有一个小小的问题。

作为一个作死专业户…我最常做的事情之一就是把explorer玩坏…然后…重新打开…

但是这里有一个尴尬的问题:当explorer死去,作为它的子窗口,我们的窗口…焉有完卵…

啊…如果你要检测自己的窗口死没死的话一定不要用Qt的isVisible什么的——窗口死去不算崩溃…

当然,写析构函数有时也会有些尴尬:如果没有WM_CLOSE的话,不会触发窗口类的析构(天知道为啥我连WM_DESTROY都没收到,第一句话就是WM_NCDESTROY)

目前看来…这种涉及到窗口崩溃之类的时候比较稳妥的处理方案是IsWindow函数…然而微软表示窗口句柄在释放后是随时可能循环利用的所以不能这么写…




那怎么办呢?

关键就在崩溃的是"explorer"这件事情上

我们再一次叫出我们的朋友spy++,来随便监视一个窗口。就拿这个网页举例子吧。

在我taskkill -f -im explorer.exe时它无动于衷。(废话,死进程不会说话)

之后,当我启动explorer时,它收到了一串消息。

这里我就取最后一个收到的消息TaskbarCreated来说吧。消息编码如下:

UINT TaskbarCreated=RegisterWindowMessageA("TaskbarCreated");之后只要聆听这个消息就好了啦…它会自动推送给所有有条件接收消息的顶级窗口的。

那么问题来了…我们的窗口崩溃之后,用什么接受啊…

只要在旁边再放一个窗口不就好了啦…

实际上,大家完全可以放置一个透明的QWidget完成这个任务…




但是我上一篇文章结尾答应了要多一些Win32的玩法嘛…

所以这里就对不起各位看官看一段经书了…

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);}首先我们构造这个窗口的窗口过程,并在其中处理消息。这里我们处理了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);大概的意思就是…除了必要的参数之外,剩下的都一律写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);这样子,我们就完成了事件监听窗口的建立。




关于QWebEngine的种种坑,因为不涉及本文“自定义Windows”的主题,因此本来是不想讨论的。

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




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

sendEvent把消息送到QWebEngineView处,它毫无反应。

后来才知道,我们应该获得QWebEngineView的子对象RenderWidgetHostViewQtDelegateWidget

关于其获取,这里我采用了这个页面中的方式:通过childPolished事件获得指定孩子

当然,对这种方式不放心的诸位,可以参照下面这个页面,枚举孩子并发送消息。

在我的做法里,我们通过如下的方式获得所需的子对象。

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);}而通过以下的方式将事件发送给孩子

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

这里我们按照MS的推荐采用CreateMutex的方式。详情请参照MSDN




好了,说了这么多…,上效果图吧

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



关键词:定义

74
73
25
news

版权所有© 亿企邦 1997-2025 保留一切法律许可权利。

为了最佳展示效果,本站不支持IE9及以下版本的浏览器,建议您使用谷歌Chrome浏览器。 点击下载Chrome浏览器
关闭