mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
116 lines
5.7 KiB
Swift
116 lines
5.7 KiB
Swift
import Foundation
|
||
|
||
struct RemotePrivacyStatus: Decodable {
|
||
let state: Int
|
||
let supported: Bool
|
||
let inputBlockSupported: Bool
|
||
let overlayActive: Bool
|
||
let inputBlocked: Bool
|
||
let remotePaused: Bool
|
||
let revision: UInt32
|
||
let reason: String
|
||
let localUnlockRevision: UInt32?
|
||
}
|
||
|
||
struct RemotePrivacyScreen {
|
||
private(set) var desktopAvailable = true
|
||
private(set) var snapshot: RemotePrivacyStatus?
|
||
var unlockNotice = false
|
||
private(set) var pending = false
|
||
private(set) var requestID: UInt32 = 0
|
||
private var requestRevision: UInt32 = 0
|
||
private var receivedAt: TimeInterval = 0
|
||
private var requestedAt: TimeInterval = 0
|
||
private var failure = ""
|
||
|
||
mutating func setInteractiveStage(_ stage: String) {
|
||
desktopAvailable = !["lock-screen", "credential-ui", "secure-desktop"].contains(stage)
|
||
}
|
||
|
||
var active: Bool {
|
||
guard let s = snapshot else { return false }
|
||
return s.overlayActive || s.remotePaused || [2, 3, 4].contains(s.state)
|
||
}
|
||
func fresh(at now: TimeInterval) -> Bool { snapshot != nil && now - receivedAt <= 5 }
|
||
func enabled(at now: TimeInterval) -> Bool {
|
||
guard let s = snapshot else { return false }
|
||
return fresh(at: now) && s.state == 3 && s.overlayActive && !s.remotePaused
|
||
}
|
||
func buttonTitle(at now: TimeInterval) -> String {
|
||
if enabled(at: now) { return NSLocalizedString("隐私屏已开启", comment: "") }
|
||
if fresh(at: now), let s = snapshot, s.supported && s.state == 1 && !active {
|
||
return NSLocalizedString("隐私屏已关闭", comment: "")
|
||
}
|
||
return NSLocalizedString("隐私屏", comment: "")
|
||
}
|
||
func feedback(at now: TimeInterval) -> String {
|
||
if !desktopAvailable { return "" }
|
||
if failure.isEmpty && (pending || fresh(at: now) && [2, 4].contains(snapshot?.state ?? -1)) {
|
||
return ""
|
||
}
|
||
if failure.isEmpty && !pending && fresh(at: now), let s = snapshot, s.supported,
|
||
enabled(at: now) || (s.state == 1 && !active) { return "" }
|
||
let reason = detail(at: now)
|
||
return status(at: now) + (reason.isEmpty ? "" : ":" + reason)
|
||
}
|
||
func canToggle(at now: TimeInterval) -> Bool {
|
||
guard let s = snapshot else { return false }
|
||
return desktopAvailable && !pending && fresh(at: now) && s.state != 4 && (active || s.supported && s.state == 1)
|
||
}
|
||
mutating func receive(_ data: Data, at now: TimeInterval) {
|
||
struct Envelope: Decodable { let type: Int; let privacyStatus: RemotePrivacyStatus }
|
||
let decoder = JSONDecoder(); decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||
guard let message = try? decoder.decode(Envelope.self, from: data), message.type == 10 else { return }
|
||
let s = message.privacyStatus
|
||
guard (0...5).contains(s.state), s.reason.utf8.count < 256,
|
||
!s.reason.contains("\0"),
|
||
s.state != 3 || (s.supported && s.overlayActive && !s.remotePaused) else { return }
|
||
if let previous = snapshot, Int32(bitPattern: s.revision &- previous.revision) < 0 { return }
|
||
if let previous = snapshot,
|
||
Int32(bitPattern: (s.localUnlockRevision ?? 0) &- (previous.localUnlockRevision ?? 0)) > 0 {
|
||
unlockNotice = true
|
||
}
|
||
if s.state == 3 { unlockNotice = false }
|
||
snapshot = s; receivedAt = now; failure = ""
|
||
if pending && Int32(bitPattern: s.revision &- requestRevision) > 0 && ![2, 4].contains(s.state) {
|
||
pending = false
|
||
}
|
||
}
|
||
func canLock(at now: TimeInterval) -> Bool {
|
||
desktopAvailable && !pending && fresh(at: now) && snapshot?.supported == true
|
||
}
|
||
mutating func begin(at now: TimeInterval, lock: Bool = false) -> UInt32? {
|
||
tick(at: now)
|
||
guard (lock ? canLock(at: now) : canToggle(at: now)), let s = snapshot else { return nil }
|
||
pending = true; requestedAt = now; requestRevision = s.revision; failure = ""
|
||
requestID &+= 1
|
||
return requestID
|
||
}
|
||
mutating func failed(request: UInt32) {
|
||
if pending && requestID == request { pending = false; failure = NSLocalizedString("指令发送失败,请重试", comment: "") }
|
||
}
|
||
mutating func tick(at now: TimeInterval) {
|
||
if pending && now - requestedAt >= 15 {
|
||
pending = false; failure = NSLocalizedString("请求超时,尚未确认电脑的隐私屏状态", comment: "")
|
||
}
|
||
}
|
||
func status(at now: TimeInterval) -> String {
|
||
if !failure.isEmpty { return failure }
|
||
guard let s = snapshot else { return NSLocalizedString("等待电脑返回隐私屏状态;旧版电脑端可能不支持", comment: "") }
|
||
if !fresh(at: now) { return NSLocalizedString("隐私屏状态已过期,等待电脑确认", comment: "") }
|
||
if s.state == 2 { return NSLocalizedString("正在开启隐私屏…", comment: "") }
|
||
if s.state == 4 { return NSLocalizedString("正在关闭隐私屏…", comment: "") }
|
||
if pending { return NSLocalizedString("指令已发送,等待电脑确认…", comment: "") }
|
||
if s.state == 5 { return NSLocalizedString("隐私屏异常", comment: "") }
|
||
if !s.supported || s.state == 0 { return NSLocalizedString("隐私屏暂不可用", comment: "") }
|
||
if s.state == 3 && s.overlayActive && !s.remotePaused {
|
||
return NSLocalizedString("隐私屏已开启", comment: "")
|
||
}
|
||
return s.state == 1 && !active ? NSLocalizedString("隐私屏已关闭", comment: "") : NSLocalizedString("隐私屏状态待确认", comment: "")
|
||
}
|
||
func detail(at now: TimeInterval) -> String {
|
||
guard let s = snapshot, fresh(at: now), s.state == 0 || s.state == 5 || !s.supported else { return "" }
|
||
return s.reason
|
||
}
|
||
}
|