这个大佬们已经分析的很透彻了,就是让自己的壁纸画布成为桌面壁纸窗口的子窗口,这样就可以让自己的壁纸覆盖windows自带的壁纸,同时有达到不干扰windows自带壁纸的目的。当然还有一个最重要的目的就是自己的壁纸层可以随意操作。
上图中,2是windows自带的壁纸窗口(或许这个说法并不准确),我们只需要将我们的壁纸窗口设置成2窗口的子窗口就达到了目的,图中3窗口就是我们自己的壁纸窗口。
然而窗口2并不太好查找,它并没有设计窗口名称,我们只知道它的类名是WorkerW,但是类名是WorkerW的窗口有很多,通过对比我们发现,我们要查找的2窗口的父窗口是图中的1窗口。而1窗口我们通过类名的窗口名很容易查找到。至此,我们查找窗口2的思路就确定了:
-
先通过类名和窗口名查找到窗口1
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就是我们查找到的目标窗口 但是默认情况下,我们可能根本找不到我们需要的这个WorkerW窗口。
具体的原因可以参考 下面这篇文章 的解释。这篇文章中也给出了处理方法,就是通过SendMessage函数向progman窗口发送0x052C的消息。这时候 Program 窗口就产生两个WorkerW窗口。我们就可以找到我们需要的窗口了。
接着我们需要将我们的壁纸画布窗口设置成background的子窗口,并将画布窗口显示出来
HWND current = (HWND)m_mask->winId();
SetParent(current,background);
m_mask->show();
这样我们的第一个目标就达成了。
虽然我们无法准确的确定wallpaper engine的鼠标动态去衣是怎样实现的。但是我们可以根据实现出来的效果反推可能的操作细节。通过推测,我得出这样的结论。
-
有两张图片,一张是带有裸漏部分,另外一张是局部地方有衣服的像素,其他的地方就是全透明的,这两张图片可以参考ps的图层效果
-
将两张图片按照合理顺序叠加(类比图层显示叠加),就可以显示带衣服的完整效果。而如果将上层的衣服区域透明化,就显示出了底层的裸漏部分。
我们实现的细节就确定了。
-
先绘制底层图片。
-
确定鼠标位置,并根据鼠标位置以一定半径确定一个圆形区域。
-
绘制上层图片,并将上层图片在圆形区域部分的透明度设置为较小值,或者干脆直接设置为全透明。
-
鼠标移动时,实时计算圆形区域,并刷新壁纸层图像。
此处遇到的第一个问题就是壁纸层我们没法聚焦,甚至没法捕捉到鼠标的事件。所以通过重新鼠标事件的方法不可行。这时候我们就要使用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()<<nCode<<","<<wParam<<","<<lParam;
return CallNextHookEx(mouseHook,nCode,wParam,lParam);//返回给下一个钩子子程处理
}
第二个问题:由于我们的壁纸窗口设置成了windows原生hwnd的子窗口,导致我们通过点击窗口上面的关闭按钮是没办法结束整个进程的。所以我们需要自定义一个关闭按钮,并掩藏windows自带窗口上面的三大金刚键。
this->setWindowFlags(Qt::FramelessWindowHint);
当然在这个设计过程中还遇到了一些其他的问题,比如显示两张图片是我最早使用的方案是用两个QLabel上下叠加来显示,但是这样在操作上很蛋疼。当然这些问题都顺利的解决掉了。
我们在使用wallpaper engine时,经常会发现一些动态图片,而这些动态图片并不是常规的gif动图,而是一个后缀名为pkg的数据包,官方称作为scene类型。这种数据类型在wallpaper engine中很常见。我们通过使用RePKG这个开源工具解开scene数据包,可以提取里面的一些基本的静态图片。通过提取出来的文件,我们大致猜测wallpaper engine的工作流程。
所谓动态图片无非就是一帧一帧的图片连续显示。 由于scene的图片动态范围和幅度一般都很小,如果我们保存原始的每一帧图片,那么压缩包会很大。由于相连帧的变化一般都很小,如果我们取相连帧的差值,并保存为图片,那么这些图像会存在大量的连续重复像素(以#000000为主),这样的图片是很容易无损压缩的。所以体积会比较小。
所以我们保存的图片只是一张最原始的图片(第一帧),然后就是第一帧和第二帧取diff值图片,然后就是第二帧和第三帧diff图片,一次类推。这样复原的时候我们只需要通过第一帧和第一个diff图片通过像素相加后取得第二张图,然后用第二张图和第二个diff图片通过像素相加后取得第三张图,一次类推就可以复原所有图片了。然后依次按照一定的时间间隔显示即可。
目前wallpaper engine的pkg包的diff规律还没有找到,我只能按照自己的理解来实现一个自己可用的算法,导致目前的软件跟wallpaper engine的scene包解析无法兼容。所以本软件的scene包需要自己手动制作(软件中集成了make制作按键,但是只能完成部分主要工作,还是需要人手动参与一部分操作,后续会完善制作工具),而无法直接导入官方scene包。
这是diff算法部分
QColor CUtils::colorDiff(const QColor &clr1, const QColor &clr2){
quint8 red = quint8(clr1.red()) - quint8(clr2.red());
quint8 green = quint8(clr1.green()) - quint8(clr2.green());
quint8 blue = quint8(clr1.blue()) - quint8(clr2.blue());
return QColor(red,green,blue);
}
QImage CUtils::imageDiff(const QImage& img1,const QImage& img2){
QImage tmpImage(img1);
if(img1.size() != img2.size()){
return tmpImage;
}
int width = img1.width();
int height = img1.height();
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
tmpImage.setPixelColor(x,y,colorDiff(img1.pixelColor(x,y),img2.pixelColor(x,y)));
}
}
return tmpImage;
}
这是合成算法部分
QColor CUtils::colorAdd(const QColor &clr1, const QColor &clr2){
quint8 red = quint8(clr1.red()) + quint8(clr2.red());
quint8 green = quint8(clr1.green()) + quint8(clr2.green());
quint8 blue = quint8(clr1.blue()) + quint8(clr2.blue());
return QColor(red,green,blue);
}
QImage CUtils::imageAdd(const QImage& img1,const QImage& img2){
QImage tmpImage(img1);
if(img1.size() != img2.size()){
return tmpImage;
}
int width = img1.width();
int height = img1.height();
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
tmpImage.setPixelColor(x,y,colorAdd(img1.pixelColor(x,y),img2.pixelColor(x,y)));
}
}
return tmpImage;
}
此处提供一个测试可用的scene包