前言

开发的动态壁纸软件:PhiWallpaper
开发环境:Python 3.11 64-bit
依赖:pywin32等

代码

核心一:如何创建动态视频窗口

这里用的是开源的ffplay作为视频窗口,仅需使用Popen()即可启动ffplay.exe进程,添加的参数使其可以做到全屏、自动裁剪视频大小、不播放视频中的音频等播放效果。这一部分参考100~120行的代码。

ffplay.exe进程是优化的重点,在启用vulkan渲染的情况下内存还能占用500MB以上的内存,相当于一个生产力软件或浏览器,对于性能而言有一定的优化空间。

核心二:如何将视频窗口嵌入壁纸

嵌入桌面必然是使用经典的0x52c消息将桌面分层,但是Windows11 24H2下的逻辑又有所不同。

首先,一定要保证ffplay窗口完全打开之后再进行嵌入修饰!这个问题困扰了我好久,最终在v0.1.0-epsilon-4版本上修复了它。如果ffplay没有完全渲染成功,直接嵌入会导致桌面存在大量黑边,这是因为ffplay默认以小窗口打开,要先小窗口打开再渲染视频为全屏。如果检测到ffplay启动后直接嵌入桌面,最终就会嵌入一个小窗口,桌面剩余部分全部黑屏。

这里使用的是51~65行编写的wait_for_render_ready()函数,并在122~128行实现等待ffplay渲染完成的逻辑,本质上是检查ffplay的窗口大小,到达一定阈值后判定完全渲染完成,再进行嵌入。

要想嵌入桌面,我们先来看一下Windows11 24H2的桌面结构,这里使用Spy++查看:
- Progman
- SHELLDLL_DefView
- SysListView32
- SysHeader32
- WorkerW(0x52c消息发出)
我们要做的就是把ffplay窗口卡在壁纸层和图标层中间。

1.查找ffplay窗口(124行)
2.查找Progman窗口(129行)
3.发送0x52c消息到Progman窗口(130行)
4.查找Progman的子窗口SHELLDLL_DefView(131行)
5.查找Progman的子窗口WorkerW(132行)
6.修饰ffplay窗口(134~145行)
7.嵌入ffplay窗口到Progman窗口(148行)
8.修饰ffplay窗口、调整层级关系(157~174行)

做完这些,就完成了动态壁纸的核心部分。

关闭动态壁纸

我使用的是最简单粗暴的方法:taskkill(186行)

修改动态壁纸

这里有一个选择文件的窗口,也是困扰了我好久。不能使用PyQt和tkinter之类的循环型GUI,否则程序会出现卡死问题,这个BUG到现在也没解决,所以我选择避开问题,直接用easygui的函数型GUI,丑是丑了一点,但耐不住它简单好用(223行)

开机自启动

这里使用的是修改注册表的方法(256~285行),“设置/取消开机自启“的按钮内部其实只有一个逻辑。按下按钮后,会尝试删除"Software\Microsoft\Windows\CurrentVersion\Run"中的”PhiWallpaper“键,如果存在,那就删除(取消开机自启动),如果不存在,那就添加(设置开机自启动)。程序默认没有开机自启动,需要在设置菜单内自行设置。

GUI

首先要让Popen不会跳出黑色的cmd窗口,所以要在进程启动信息内添加"不显示窗口"(296~298行)。
其次在打包时要注意使用GUI程序参数,我是用的是pyinstaller,所以参数是-w。

由于动态壁纸是一个比较沉浸式的程序,所以我在主程序上使用的是较为隐蔽的系统托盘(314~321行),所有需要用到窗口的地方(如提示、选择文件等)就用函数式的easygui。使用GUI循环的GUI似乎会阻塞主程序,我原先用了多线程来尝试解决,但是效果不佳,所以最后还是只有一个主进程,没开线程,有需要外部程序的地方都用Popen异步开进程,不会干扰主进程。

结语

主要的问题就是这些啦......如果你仔细看我附上的代码,就会发现还有很多细节的地方,只不过他们并不是那么重要。动态壁纸应用已经同步上传至Github,你可以搜索PhiWallpaper或者打开文章开头的链接下载使用,能给个Star的话就更好了~