ARTICLE · 1153535
egui 写桌面软件的坑:一个截图功能,我返工了三次
"截图不行,你再试试。"——用户一句话,引出高 DPI 下的一串深坑,最后用 706 行 Win32 代码收场。
需求很朴素:给局域网 IM 加个微信式区域截图——点按钮,全屏出现画面,拖框选区,松开发送。
GUI 框架是 egui(Rust)。我当时觉得这是半天的活。
事实证明,对了一半:最后确实做了三个半天。
第一次翻车:窗口只盖住了三分之二的屏幕
egui 支持多视口(可以开独立子窗口),第一版很自然地用它做全屏覆盖层,创建时直接声明"全屏 + 无边框 + 置顶"。
自测全通:回调执行了、画面纹理上传了、模拟选区后裁剪落盘了。
但用户实测:点了没反应。
用 Windows API 去探测这个窗口的真相,结果大跌眼镜:
窗口存在吗?在。
可见吗?可见。
尺寸多大?1680×1120 像素。
而屏幕实际是 2520×1680(系统 150% 缩放)。
窗口只盖住了左上角的 2/3——egui 子视口按"逻辑尺寸"建窗,没吃到 DPI 缩放。
在 100% 缩放的测试机上它完全正常;而国内办公笔记本几乎全是 125%/150% 缩放,它就悄悄地错了。错得非常"礼貌":没有任何报错,窗口就在那儿,用户感知就是"没反应"。
第二次翻车:命令发出去了,窗口纹丝不动
第二版换了思路:不再开子窗口,直接把主窗口临时切全屏,用 egui 提供的视口命令驱动——
发"全屏"命令,发"置顶"命令,发"抢焦点"命令。
探测结果:命令全部发出去了,窗口风格一个字节都没变。
底层窗口库(winit)对这批命令的处理,在这个组合下就是不可靠。
两次教训合并成一个结论:
框架里"窗口级"的强操作——全屏、置顶、精确摆位——在高 DPI 下不可信。要做全屏覆盖层,必须绕开框架,直接和操作系统对话。
第三版:绕开框架,直接 Win32
参考 ShareX、Flameshot 的做法:开一个纯 Win32 窗口,跑自己的消息循环,用 GDI 直接画截图。
关键就三步:
注册窗口类,创建"无边框 + 置顶 + 不进任务栏"的弹层,按物理像素摆满整个屏幕——DPI 虚拟化与我们无关,我们自己在物理坐标系里算。
把截屏的 RGBA 数据转成 GDI 能用的位图格式。
自己跑消息循环:鼠标移动画选框,键盘处理 Esc 取消、回车确认、方向键微调。
一次就对了。交互还顺手补全了企业工具该有的样子:框内拖动移动选区、方向键微调 1 像素(按 Shift 是 10 像素)、选区外压暗选区内全亮。
移植路上踩的三个经典坑
这三个坑全部来自前辈项目的实机注释,每条都救了我一次:
坑一:GDI 做不了半透明。 想给选区外盖半透明黑遮罩?GDI 的填充是完全覆盖,硬做出来的效果是全黑。正确姿势:预先烘焙一帧"每像素调暗到 35%"的图,选区外铺它,选区内铺原图。
坑二:有个对象创建时机不对,所有绘制静默失败。 某个 GDI 位图必须在释放屏幕设备句柄之前创建,顺序反了它会创建失败,而后续所有绘制不报任何错——窗口存在、可见、在前台,就是从来没画出过内容。这种"礼貌性失败"是最恶心的调试对象。
坑三:这类无边框窗口创建后必须显式"显示"。 它默认不带可见属性,不显式显示的话窗口藏着、消息循环活着,用户看到的依然是"点了没反应"。
还有个视觉细节:这类窗口初始画面是纯黑,直接显示会"先黑一下再出图"。解法是显示前先画一帧,出现的瞬间就是完整画面。
让"你试试"变成"它自己试"
三次返工,逼我把验证流程也固化了:加了个自测开关,启动时自动触发截图流程,预设选区,两秒半后模拟回车确认。
配合 Windows API 探测,每次改动能拿到完整证据链:
整屏捕获成功(2520×1680)
框选窗口已创建并显示
截图已保存(1890×1176)
探测确认:可见、无边框、置顶、在前台——四项全绿。
GUI 功能没法写单元测试?可以拆成"能测的"(捕获、裁剪、落盘)和"只能探测的"(窗口状态),后者用系统 API 断言,比人肉点靠谱得多。
复盘
高 DPI 不是边缘情况。 国内办公机默认就是 125%/150%,按逻辑坐标硬算的窗口代码会在这类机器上集体翻车——而 100% 缩放的测试机上你连复现都复现不了。
框架的能力边界要提前探明。 egui 画界面很好,但窗口管理是"操作系统的事",该还给操作系统。
"没反应"是最需要证据的故障。 窗口存在吗?可见吗?多大?在前台吗?四个系统调用就能把玄学变成科学。别猜。
项目开源在 Gitee:Rust 写的局域网 IM,单二进制免安装,支持飞鸽协议互通、跨子网发现、微信式文件传输。
项目地址(复制到浏览器打开):
https://gitee.com/zhongyifeng-git-reporter/local-network-chat-tool
踩过同款坑的同学,点个在看让我看到你。下一篇聊微信式文件传输的协议设计。