Windows下需用pywin32在WM_QUERYENDSESSION消息中弹出确认对话框并返回0(拒绝)或1(允许),以此拦截关机;macOS和Linux无法可靠拦截系统级关机事件。

Windows下用win32api捕获WM_QUERYENDSESSION消息
Python标准库不提供跨平台的关机事件拦截能力,Windows平台必须依赖win32api(pywin32)注册窗口消息处理器。关键不是“阻止关机”,而是收到WM_QUERYENDSESSION时弹出确认对话框——系统会根据返回值决定是否继续关机流程。
常见错误是直接监听WM_ENDSESSION:它只在关机已开始后触发,此时无法中止;而WM_QUERYENDSESSION是系统询问“是否允许关机”的唯一时机,必须在此返回0(拒绝)才能弹窗提示。
实操建议:
- 用
win32gui.PumpMessages()维持消息循环,不能依赖time.sleep()阻塞 - 注册消息前需创建隐藏窗口(
win32gui.CreateWindow),无窗口无法接收系统消息 - 回调函数中若用户选择“取消”,必须返回
0;选“保存并关机”则返回1,否则系统可能重复询问或强制终止进程 - 注意UAC权限:若程序以管理员身份运行,而关机由普通用户发起,部分场景下消息可能被截断
macOS和Linux无法可靠拦截关机事件
macOS没有等价于WM_QUERYENDSESSION的API,NSApplication.terminate:是应用级退出信号,无法捕获系统级关机(如电源按钮、sudo shutdown)。Linux下systemd的StopWhenUnneeded=yes或ExecStop=仅控制服务停止顺序,不提供“询问用户”机制。
立即学习“Python免费学习笔记(深入)”;
实际能做的只有被动响应:
- Linux:监听
SIGTERM(signal.signal(signal.SIGTERM, handler)),但该信号在关机流程后期发送,留给保存的时间极短(通常 - macOS:注册
NSApplicationWillTerminateNotification通知,同样属于应用退出阶段,非关机预检 - 所有Unix-like系统中,
atexit.register()可执行清理,但无法弹窗或等待用户输入——终端无焦点、GUI无响应上下文
跨平台方案只能退守到“优雅降级”
没有真正跨平台的关机拦截方案。所谓“通用做法”本质是分层防御:优先尝试平台特有机制,失败则降级为自动保存+快速退出。
关键设计点:
- 把数据保存逻辑抽离为独立函数(如
save_user_data()),确保SIGTERM、atexit、GUI关闭事件都能调用它 - 用文件锁或临时标记文件(如
.unsaved.tmp)记录未保存状态,下次启动时检查并提示恢复 - 避免在关机处理中做耗时操作(如网络请求、大文件写入),Linux/macOS下超时会被
SIGKILL强杀 - Windows上若使用PyQt/PySide,可通过
QApplication.aboutToQuit连接槽函数,但它比WM_QUERYENDSESSION晚,仅作补充
PyQt示例中容易忽略的Qt.AA_EnableHighDpiScaling兼容问题
用PyQt实现带GUI提示的关机拦截时,若开启高DPI支持(Qt.AA_EnableHighDpiScaling),在Windows多显示器混合缩放环境下,QMessageBox可能弹出在不可见区域或尺寸异常,导致用户误以为程序卡死。
解决方案:
- 在创建
QApplication后立即设置setAttribute(Qt.AA_DisableHighDpiScaling),牺牲清晰度保功能可用性 - 或改用
QDialog自定义对话框,并显式调用move(QApplication.primaryScreen().availableGeometry().center() - dialog.rect().center())居中 - 测试时务必在125%/150%缩放的副屏上验证弹窗位置,仅主屏测试会漏掉此问题
真正的难点不在代码怎么写,而在关机事件本身不具备交互能力——你永远无法保证提示框一定被看到、被点击。最稳妥的做法,是让保存动作足够快、足够静默,而不是依赖用户在倒计时里做选择。


















