最近搞了一个人脸解锁 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 权限,且要在监测执行之前用户就以勾选授权,否则会无法监测,所以需要尽早请求授权以及尽量晚地进行监测。