windows通过setthreadexecutionstate请求延迟挂起,linux用systemd-inhibit或dbus申请inhibit锁,macos以iopmassertioncreatewithname创建断言阻止屏幕休眠,三者均非真正拦截且需严格配对清理。

Windows 上如何拦截系统挂起(Sleep)事件
Windows 不允许普通程序“阻止”挂起,但可以请求系统延迟挂起,直到你完成关键操作。核心是 SetThreadExecutionState,它不是拦截器,而是“续命信号”。常见错误是调用后不重置,导致系统长期无法进入睡眠——尤其在服务或后台线程中容易被忽略。
典型使用场景:播放视频、文件拷贝、OTA 升级写盘时防止意外休眠。必须成对调用,且建议用 RAII 封装:
class SleepBlocker {
bool active_ = false;
public:
void keepAwake() {
if (!active_) {
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED);
active_ = true;
}
}
void allowSleep() {
if (active_) {
SetThreadExecutionState(ES_CONTINUOUS); // 仅恢复默认行为
active_ = false;
}
}
~SleepBlocker() { allowSleep(); }
};
-
ES_SYSTEM_REQUIRED阻止系统进入睡眠,但不阻止屏幕关闭 -
ES_AWAYMODE_REQUIRED用于媒体/游戏等需后台持续运行的场景(需系统支持) - 不能在 DLL 的
DllMain或低权限沙箱进程里调用,会静默失败 - 每次调用只影响当前线程,多线程需各自管理
Linux 下如何响应 systemd 的 suspend 信号
Linux 没有“拦截”概念,而是通过 systemd-inhibit 命令或 D-Bus 接口协商延迟。直接监听 PrepareForSleep 信号只能“感知”,不能拒绝——想“拦住”必须提前申请 inhibit lock。
最可靠方式是调用 D-Bus 方法 Inhibit,传入 reason、mode、what 和 who。常见坑是未指定 what=handle-lid-switch:suspend 等具体子项,导致锁无效;或忘记在进程退出前释放锁,造成后续 suspend 卡住。
- 推荐用
systemd-inhibit --what=handle-lid-switch:suspend --who="MyApp" --why="Copying large file" ./myapp启动主程序 - 若需动态控制,用
dbus-send或 libsystemd 提交 inhibit 请求,返回一个 fd,关闭它即释放锁 - 注意:
handle-lid-switch和handle-power-key是不同 what 值,混用无效 - Ubuntu 22.04+ 默认启用 logind 的 idle inhibition,但需
IdleHint=false配合,否则仍可能被强制 suspend
macOS 上如何阻止 display sleep 而非 system sleep
macOS 对系统级 sleep 几乎不开放干预接口,但允许阻止 display sleep(屏幕熄灭)和 disk sleep。真正能用的只有 IOPMAssertionCreateWithName,它创建的是“断言(assertion)”,不是钩子。所谓“拦截”其实是告诉 powerd:“我还有活儿没干完,请别关屏”。
关键点在于 assertion 类型选择:kIOPMAssertionTypePreventDisplaySleep 最常用,但无法阻止笔记本合盖休眠;若要阻止合盖,必须用 kIOPMAssertionTypePreventSystemSleep,且需用户授权(TCC 权限),否则静默失败。
- 调用后必须保存返回的
CFStringRefassertionID,并在完成后调用IOPMAssertionRelease - 未 release 的 assertion 会在进程退出时自动清理,但依赖进程正常终止——崩溃或 kill -9 会导致 assertion 残留,需手动
pmset -g assertions查看并重启 powerd - macOS 13+ 对 background 进程的 assertion 时长有限制(默认 30 分钟),超时后自动失效
- 不要在主线程长时间持有 assertion,否则影响 AppKit 事件循环响应
跨平台封装要注意的三个硬约束
没有统一 API,各平台语义差异大:Windows 是“续命”,Linux 是“协商锁”,macOS 是“断言”。强行抽象成一个 BlockSleep() 接口容易掩盖行为差异,反而引发线上问题。
- Windows 的
SetThreadExecutionState是 per-thread,Linux/macOS 的机制是 per-process,多线程应用需统一协调 owner - Linux 的 inhibit lock 可被其他进程覆盖(优先级更低的 lock 会被踢掉),而 Windows/macOS 的 assertion 是“最后提交者胜”,无抢占逻辑
- 所有平台都不保证 100% 拦截:用户长按电源键、内核 panic、电池耗尽等场景下,任何软件层干预都会失效
真正稳定的方案是分层处理:UI 层提示用户“正在执行关键操作,请勿合盖/按电源键”,底层只做尽力而为的延迟保障。最易被忽略的是 cleanup 路径——异常退出、SIGKILL、crash 时 assertion/inhibit 是否能自动回收,这决定了用户下次开机是否发现电脑莫名无法休眠。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











