最近一直在折腾一下壁纸的东西,前段时间刚写了一个跨平台桌面(windows/linux kde)的壁纸网络应用,个人使用效果还不错的样子。
地址
前两天突然发现了wallpaper engine这个软件,然后在创意工坊中打开了新世界,感觉挺炫酷的。于是想着看自己能不能照着实现一些类似的特性。于是就有了这个工程。
本项目地址
本工程基于QT实现,涉及到一些Windows的原生API操作,请查看微软doc文档。
这个大佬们已经分析的很透彻了,就是让自己的壁纸画布成为桌面壁纸窗口的子窗口,这样就可以让自己的壁纸覆盖windows自带的壁纸,同时有达到不干扰windows自带壁纸的目的。当然还有一个最重要的目的就是自己的壁纸层可以随意操作。
上图中,2是windows自带的壁纸窗口(或许这个说法并不准确),我们只需要将我们的壁纸窗口设置成2窗口的子窗口就达到了目的,图中3窗口就是我们自己的壁纸窗口。
然而窗口2并不太好查找,它并没有设计窗口名称,我们只知道它的类名是WorkerW,但是类名是WorkerW的窗口有很多,通过对比我们发现,我们要查找的2窗口的父窗口是图中的1窗口。而1窗口我们通过类名的窗口名很容易查找到。至此,我们查找窗口2的思路就确定了:
HWND hwnd = ::FindWindowA("progman","Program Manager");
然后循环遍历查找类名为WorkerW,并判断查找到的窗口的父窗口,是不是上面确定的hwnd窗口,如果是的,那么查找就结束了。
HWND background = NULL;
HWND hwnd = ::FindWindowA("progman","Program Manager");
HWND worker = NULL;
do{
worker = ::FindWindowExA(NULL,worker,"WorkerW",NULL);
if(worker!=NULL){
char buff[200] = {0};
int ret = GetClassName(worker,(WCHAR*)buff,sizeof(buff)*2);
if(ret == 0){
int err = GetLastError();
qDebug()<<"err:"<<err;
}
//QString className = QString::fromUtf16((char16_t*)buff);
}
if(GetParent(worker) == hwnd){
background = worker;
}
}while(worker !=NULL);
上面代码中background就是我们查找到的目标窗口
接着我们需要将我们的壁纸画布窗口设置成background的子窗口,并将画布窗口显示出来
HWND current = (HWND)m_mask->winId();
SetParent(current,background);
m_mask->show();
这样我们的第一个目标就达成了。
虽然我们无法准确的确定wallpaper engine的鼠标动态去衣是怎样实现的。但是我们可以根据实现出来的效果反推可能的操作细节。通过推测,我得出这样的结论。
我们实现的细节就确定了。
此处遇到的第一个问题就是壁纸层我们没法聚焦,甚至没法捕捉到鼠标的事件。所以通过重新鼠标事件的方法不可行。这时候我们就要使用windows的事件钩子函数,来注册全局的事件钩子。
mouseHook =SetWindowsHookEx( WH_MOUSE_LL,mouseProc,GetModuleHandle(NULL),NULL);//注册鼠标钩子
然后在钩子函数中确定鼠标的实时位置,并计算以鼠标为中心的圆形区域,然后刷新壁纸窗口。
LRESULT CALLBACK mouseProc(int nCode,WPARAM wParam,LPARAM lParam )
{
if(nCode == HC_ACTION) //当nCode等于HC_ACTION时,要求得到处理
{
if(wParam==WM_MOUSEMOVE)//鼠标的移动
{
POINT p;
GetCursorPos(&p);//获取鼠标坐标
CMask* mask = w->getMask();
mask->setMask(p.x,p.y);
//双薪壁纸
mask->update();
}
}
//qDebug()<
return CallNextHookEx(mouseHook,nCode,wParam,lParam);//返回给下一个钩子子程处理
}
第二个问题:由于我们的壁纸窗口设置成了windows原生hwnd的子窗口,导致我们通过点击窗口上面的关闭按钮是没办法结束整个进程的。所以我们需要自定义一个关闭按钮,并掩藏windows自带窗口上面的三大金刚键。
this->setWindowFlags(Qt::FramelessWindowHint);
当然在这个设计过程中还遇到了一些其他的问题,比如显示两张图片是我最早使用的方案是用两个QLabel上下叠加来显示,但是这样在操作上很蛋疼。当然这些问题都顺利的解决掉了。