判断 Mac 是否处于密码输入界面 | Swift

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

判断 Mac 是否处于密码输入界面 | Swift

http://www.zh0ngtian.tech/posts/eef7c008.html

作者

zhongtian

发布于

2020-01-29

更新于

2023-12-16

许可协议

评论