Wallpaper Engine特性仿制

wallpaper master

起源

最近一直在折腾一下壁纸的东西,前段时间刚写了一个跨平台桌面(windows/linux kde)的壁纸网络应用,个人使用效果还不错的样子。

地址

前两天突然发现了wallpaper engine这个软件,然后在创意工坊中打开了新世界,感觉挺炫酷的。于是想着看自己能不能照着实现一些类似的特性。于是就有了这个工程。

本项目地址

预览

特性

  • 无干扰的独立壁纸层
  • 鼠标跟随实时去衣

本工程基于QT实现,涉及到一些Windows的原生API操作,请查看微软doc文档。

无干扰的独立壁纸层

这个大佬们已经分析的很透彻了,就是让自己的壁纸画布成为桌面壁纸窗口的子窗口,这样就可以让自己的壁纸覆盖windows自带的壁纸,同时有达到不干扰windows自带壁纸的目的。当然还有一个最重要的目的就是自己的壁纸层可以随意操作。

上图中,2是windows自带的壁纸窗口(或许这个说法并不准确),我们只需要将我们的壁纸窗口设置成2窗口的子窗口就达到了目的,图中3窗口就是我们自己的壁纸窗口。

然而窗口2并不太好查找,它并没有设计窗口名称,我们只知道它的类名是WorkerW,但是类名是WorkerW的窗口有很多,通过对比我们发现,我们要查找的2窗口的父窗口是图中的1窗口。而1窗口我们通过类名的窗口名很容易查找到。至此,我们查找窗口2的思路就确定了:

  1. 先通过类名和窗口名查找到窗口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就是我们查找到的目标窗口
接着我们需要将我们的壁纸画布窗口设置成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()<
    return CallNextHookEx(mouseHook,nCode,wParam,lParam);//返回给下一个钩子子程处理
}

第二个问题:由于我们的壁纸窗口设置成了windows原生hwnd的子窗口,导致我们通过点击窗口上面的关闭按钮是没办法结束整个进程的。所以我们需要自定义一个关闭按钮,并掩藏windows自带窗口上面的三大金刚键。

this->setWindowFlags(Qt::FramelessWindowHint);

当然在这个设计过程中还遇到了一些其他的问题,比如显示两张图片是我最早使用的方案是用两个QLabel上下叠加来显示,但是这样在操作上很蛋疼。当然这些问题都顺利的解决掉了。

你可能感兴趣的:(qt)