最近搞了一个人脸解锁 mac 的程序 FaceLock ,其中需要判断 Mac 是否处于密码输入界面,为了正确识别这个状态,需要对锁屏相关的几种状态变化做一个梳理。
系统睡眠、屏幕睡眠、屏保程序、密码输入界面和锁屏之间的关系 严格来讲,系统睡眠、屏幕睡眠、屏保程序、输入密码界面都属于锁屏,至少从系统事件角度来看是这样的:如果监测 com.apple.screenIsLocked 事件,进入以上四种状态都是会响应的。下面我用示意图来说明这四种状态和桌面状态的转换关系。
1 2 3 4 5 6 7 8 9 10 graph TD A((密码输入界面)) -->|解锁| B(桌面) C(屏幕睡眠) -->|唤醒| A D(系统睡眠) -->|唤醒| C E(屏保程序) -->|唤醒| A B -->|锁定屏幕| A B -->|将显示器置于睡眠状态| C B -->|启动屏幕保护程序| E C -->|经过一段时间后| D A -->|经过 5 秒后| C 
1 2 3 4 graph TD A(桌面) -->|经过屏幕保护程序开始前闲置时间 t1 后| B(屏保程序) B -->|经过 t2 - t1 后| C(屏幕睡眠) A --> |经过节能设置项中的时间 t2 后| C 
监测 Mac 是否进入密码输入界面 macOS 并没有提供“密码输入界面”这个系统事件,所以想要判断当前 Mac 是否进入了这个状态,就需要通过别的状态发生变化来实现。密码输入界面有三种方式可以进入(由于进入系统睡眠前一定会发生屏幕睡眠、退出系统睡眠后一定会退出屏幕睡眠,所以可以仅仅使用屏幕睡眠来判断状态,下面使用“睡眠”代指屏幕睡眠):
从桌面中进入(手动执行“锁定屏幕”操作)
系统会识别为锁屏,但是不知道具体是哪种状态,需要排除睡眠和屏保 
可以再进入睡眠和屏保时会设定一个状态变量,检查这个变量即可 
 
 
从睡眠中退出
 
从屏保程序中退出
只要处于屏保状态中有任何键盘或者鼠标操作 
注意:只有解锁进入桌面系统才算是结束屏保程序,所以这里不能通过监测屏保程序的结束来判断,只能通过监测键盘鼠标操作 
 
 
 
监测方法 屏幕锁定与解锁 1 2 3 4 5 6 7 8 9 @objc  func  onLock () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Screen is locked" ) } @objc  func  onUnlock () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Screen is unlocked" ) } let  dnc =  DistributedNotificationCenter .default()dnc.addObserver(self , selector: #selector (onLock), name: NSNotification .Name (rawValue: "com.apple.screenIsLocked" ), object: nil ) dnc.addObserver(self , selector: #selector (onUnlock), name: NSNotification .Name (rawValue: "com.apple.screenIsUnlocked" ), object: nil ) 
屏保程序开始与结束 1 2 3 4 5 6 7 8 9 10 11 12 13 @objc  func  onScreensaverDidStart () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Screensaver did start" ) } @objc  func  onScreensaverWillStop () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Screensaver will stop" ) } @objc  func  onScreensaverDidStop () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Screensaver did stop" ) } let  dnc =  DistributedNotificationCenter .default()dnc.addObserver(self , selector: #selector (onScreensaverDidStart), name: NSNotification .Name (rawValue: "com.apple.screensaver.didstart" ), object: nil ) dnc.addObserver(self , selector: #selector (onScreensaverWillStop), name: NSNotification .Name (rawValue: "com.apple.screensaver.willstop" ), object: nil ) dnc.addObserver(self , selector: #selector (onScreensaverDidStop), name: NSNotification .Name (rawValue: "com.apple.screensaver.didstop" ), object: nil ) 
屏幕睡眠与唤醒 1 2 3 4 5 6 7 8 9 @objc  func  onDisplaySleep () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Display sleep" ) } @objc  func  onDisplayWake () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> Display wake" ) } let  nc =  NSWorkspace .shared.notificationCenternc.addObserver(self , selector: #selector (onDisplaySleep), name: NSWorkspace .screensDidSleepNotification, object: nil ) nc.addObserver(self , selector: #selector (onDisplayWake), name: NSWorkspace .screensDidWakeNotification, object: nil ) 
系统睡眠与唤醒 1 2 3 4 5 6 7 8 9 @objc  func  onSystemSleep () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> System sleep" ) } @objc  func  onSystemWake () {    print ("\(Date(timeIntervalSinceNow: 0 ))  -> System wake" ) } let  nc =  NSWorkspace .shared.notificationCenternc.addObserver(self , selector: #selector (onSystemSleep), name: NSWorkspace .WillSleepNotification , object: nil ) nc.addObserver(self , selector: #selector (onSystemWake), name: NSWorkspace .DidWakeNotification , object: nil ) 
键盘敲击或者鼠标移动 1 2 3 NSEvent .addGlobalMonitorForEvents(matching: [.keyDown, .mouseMoved]) { _  in     print ("mouse moved" ) } 
监测鼠标移动事件不需要 Accessibility 权限,监测键盘敲击事件需要 Accessibility 权限,且要在监测执行之前用户就以勾选授权,否则会无法监测,所以需要尽早请求授权以及尽量晚地进行监测。