应使用系统提供的会话状态通知机制:windows 用 wtsregistersessionnotification 监听 wm_wtssession_change 消息,macos 用 cgsessioncopycurrentdictionary 轮询 kcgsessionislockedkey 并辅以 iokit 监听,linux 通过 systemd-logind d-bus 接口监听 locksession/unlocksession 信号。

Windows 上怎么检测用户是否锁屏?用 WTSRegisterSessionNotification 而不是轮询 GetAsyncKeyState
直接轮询键盘或鼠标状态来“猜”锁屏是错的——锁屏后系统仍可能有后台输入(比如远程桌面唤醒),而且 GetAsyncKeyState 在服务进程里根本不可靠。Windows 提供了会话状态变更通知机制,这才是正路。
关键点:必须在 UI 线程注册,且窗口需处理 WM_WTSSESSION_CHANGE 消息;不能在无窗口的控制台程序或服务里直接用(除非自己创建隐藏窗口)。
-
WTSRegisterSessionNotification需传入一个 HWND,所以哪怕你写的是控制台程序,也得创建一个不可见窗口(用CreateWindowEx+WS_POPUP) - 注册后,锁屏/解锁会发
WM_WTSSESSION_CHANGE,wParam是WTS_SESSION_LOCK或WTS_SESSION_UNLOCK - 别忘了调用
WTSUnregisterSessionNotification清理,否则下次注册失败 - 注意:该 API 从 Windows XP 开始支持,但 Vista+ 才稳定;Win7 及以后无需管理员权限
macOS 怎么拿到屏幕锁定事件?靠 CGSession + IORegistry 监听,不是 ScreenSaver 框架
macOS 没有类似 Windows 的会话通知,ScreenSaver 框架只管屏保启动,和系统锁屏(如 Cmd+Ctrl+Q)无关。真实锁屏由 loginwindow 进程触发,信号藏在 IOKit 的 IORegistry 里,同时 CGSession 提供轻量级轮询接口。
推荐组合:用 CGSessionCopyCurrentDictionary 获取当前会话状态(含 kCGSessionOnConsoleKey 和 kCGSessionIsLockedKey),再配合 IOPMConnectionCreate 监听电源/显示状态变化作为辅助判断。
立即学习“C++免费学习笔记(深入)”;
-
CGSessionCopyCurrentDictionary返回字典,查kCGSessionIsLockedKey的布尔值即可,每秒查一次足够(别高频调用) - 纯监听方案要用
IOServiceAddInterestNotification订阅IOPlatformExpertDevice的IOGeneralInterest,但实现复杂、易漏事件 - 注意:沙盒应用无法访问
CGSessionAPI,需在entitlements中添加com.apple.security.temporary-exception.mach-lookup.global-name(不推荐)或改用无沙盒进程协作 - macOS 12+ 对
CGSession的调用更敏感,建议加@autoreleasepool防止内存泄漏(C++ 混编 ObjC 时)
Linux 下没有统一锁屏 API,logind D-Bus 接口是唯一靠谱选择
X11 或 Wayland 自身都不暴露锁屏状态,桌面环境(GNOME/KDE)各自为政。唯一跨桌面的途径是通过 systemd-logind 的 D-Bus 接口,它由 logind 守护进程统一管理会话生命周期。
重点监听 org.freedesktop.login1.Manager 的 LockSession 和 UnlockSession 信号,或者轮询 GetSession 方法返回的 LockedHint 属性。
- 必须连接到系统总线(
dbus_system_bus_get),不是会话总线;普通用户默认有权限,但某些发行版(如 RHEL/CentOS)需在/etc/dbus-1/system.d/配置策略 - 信号监听比轮询更及时,但首次连接后要先调用
ListSessions获取当前状态,避免启动时错过初始锁态 - Wayland 下部分场景(如 GNOME 的 “锁屏后黑屏”)可能延迟几秒才发出信号,建议加 500ms 去抖
- 别依赖
XScreenSaverQueryInfo—— 它只反映屏保,且在 Wayland 上完全失效
C++ 跨平台封装要注意什么?别把平台逻辑塞进同一个函数里
最常见错误是写一个 isSystemLocked() 函数,里面用 #ifdef 堆砌三套逻辑,结果 Windows 编译过、macOS 运行崩溃、Linux 测试时发现 D-Bus 连接超时没处理。
真正可行的做法是分层:抽象出 LockStateObserver 接口,每个平台实现独立类(WindowsLockObserver、MacOSSessionObserver、LinuxLogindObserver),初始化时动态选型。
- 各平台初始化失败(如 macOS 没权限、Linux D-Bus 不可用)必须返回明确错误,不能静默降级为“始终未锁屏”
- 锁屏状态不是瞬时值,而是带时间戳的事件流;缓存最新状态 + 更新时间,避免频繁查询引发性能抖动
- Windows 的
WM_WTSSESSION_CHANGE可能被其他消息淹没,确保消息循环不阻塞;macOS 的CGSession调用要放在主线程(GCD main queue);Linux 的 D-Bus 回调必须用异步 dispatch 避免阻塞主循环 - 测试时最容易忽略的是“锁屏中重启”场景:Windows 会话可能变成
WTSDisconnected,macOS 会话字典可能为空,Linux 的logind会话 ID 可能重置——这些都要单独处理









