151 lines
4.6 KiB
Swift
151 lines
4.6 KiB
Swift
import Foundation
|
||
import Combine
|
||
import IOKit.pwr_mgt
|
||
|
||
// MARK: - App State Manager
|
||
class AppState: ObservableObject {
|
||
// MARK: - Published Properties (UI 会观察这些属性)
|
||
|
||
// 功能的总开关
|
||
@Published var isFunctionEnabled: Bool = false
|
||
|
||
// 用户当前是否空闲
|
||
@Published var isUserIdle: Bool = false
|
||
|
||
// 内部属性
|
||
private var monitorTimer: Timer?
|
||
private var caffeinateProcess: Process?
|
||
|
||
// 配置参数
|
||
private let idleThreshold: TimeInterval = 300 // 5分钟无操作视为空闲
|
||
private let checkInterval: TimeInterval = 10 // 每10秒检查一次
|
||
|
||
// MARK: - Public Methods
|
||
|
||
/// 切换主功能的开关
|
||
func toggleFunction() {
|
||
isFunctionEnabled.toggle()
|
||
if isFunctionEnabled {
|
||
// 开启功能,立即开始一轮检查,并设置定时器
|
||
performCheck()
|
||
startMonitoringTimer()
|
||
} else {
|
||
// 关闭功能,停止一切
|
||
stopMonitoringTimer()
|
||
stopCaffeinate()
|
||
updateUserIdleStatus(false) // 重置状态
|
||
}
|
||
}
|
||
|
||
// MARK: - Monitoring Logic
|
||
|
||
private func startMonitoringTimer() {
|
||
guard monitorTimer == nil else { return }
|
||
print("开始监控...")
|
||
// 使用 weak self 避免循环引用
|
||
monitorTimer = Timer.scheduledTimer(withTimeInterval: checkInterval, repeats: true) { [weak self] _ in
|
||
self?.performCheck()
|
||
}
|
||
}
|
||
|
||
private func stopMonitoringTimer() {
|
||
monitorTimer?.invalidate()
|
||
monitorTimer = nil
|
||
print("监控已停止。")
|
||
}
|
||
|
||
private func performCheck() {
|
||
// 1. 获取系统空闲时间
|
||
let idleTime = getSystemIdleTime()
|
||
|
||
// 2. 判断是否空闲
|
||
let isIdleNow = (idleTime >= idleThreshold)
|
||
|
||
// 3. 如果用户状态发生了变化,则采取行动
|
||
if isIdleNow != isUserIdle {
|
||
updateUserIdleStatus(isIdleNow)
|
||
|
||
if isIdleNow {
|
||
// 从“活动中”变为“空闲”
|
||
stopCaffeinate()
|
||
} else {
|
||
// 从“空闲”变为“活动中”
|
||
startCaffeinate()
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - User Idle Detection
|
||
|
||
/// 使用 IOKit 获取系统空闲时间 (键盘、鼠标、触控板无操作)
|
||
/// - Returns: 空闲时间(秒)
|
||
private func getSystemIdleTime() -> TimeInterval {
|
||
// 这是一个更可靠、更原生的方法
|
||
var onPort: mach_port_t = 0
|
||
var prop: Unmanaged<CFTypeRef>?
|
||
|
||
// 获取 IO Master Port
|
||
let kr = IOMainPort(0, &onPort)
|
||
guard kr == KERN_SUCCESS else { return 0 }
|
||
|
||
// 查找电源管理服务
|
||
let powerService = IOServiceGetMatchingService(onPort, IOServiceMatching("IOPMPowerSource"))
|
||
guard powerService != 0 else { return 0 }
|
||
|
||
// 尝试获取空闲时间属性
|
||
let result = IORegistryEntryCreateCFProperty(powerService, "HIDIdleTime" as CFString, kCFAllocatorDefault, 0)
|
||
IOObjectRelease(powerService)
|
||
|
||
guard let data = result?.takeRetainedValue() as? Data else { return 0 }
|
||
|
||
// 数据通常是大端序的 UInt64
|
||
var idleTime: UInt64 = 0
|
||
(data as NSData).getBytes(&idleTime, length: MemoryLayout<UInt64>.size)
|
||
|
||
return Double(idleTime) / 1_000_000_000.0 // 纳秒转秒
|
||
}
|
||
|
||
// MARK: - Caffeinate Management
|
||
|
||
private func startCaffeinate() {
|
||
guard caffeinateProcess == nil else { return }
|
||
|
||
print("用户活动,启动 caffeinate 阻止睡眠...")
|
||
caffeinateProcess = Process()
|
||
caffeinateProcess?.executableURL = URL(fileURLWithPath: "/usr/bin/caffeinate")
|
||
caffeinateProcess?.arguments = ["-d"] // -d 指示系统不要关闭显示屏
|
||
|
||
do {
|
||
try caffeinateProcess?.run()
|
||
} catch {
|
||
print("启动 caffeinate 失败: \(error)")
|
||
caffeinateProcess = nil
|
||
}
|
||
}
|
||
|
||
private func stopCaffeinate() {
|
||
guard caffeinateProcess != nil else { return }
|
||
|
||
print("用户空闲或功能关闭,停止 caffeinate.")
|
||
caffeinateProcess?.terminate()
|
||
caffeinateProcess = nil
|
||
}
|
||
|
||
// MARK: - UI State Update
|
||
|
||
/// 在主线程更新用户空闲状态
|
||
private func updateUserIdleStatus(_ isIdle: Bool) {
|
||
DispatchQueue.main.async {
|
||
self.isUserIdle = isIdle
|
||
}
|
||
}
|
||
|
||
// MARK: - Deinitializer
|
||
|
||
deinit {
|
||
// 确保 App 退出时清理资源
|
||
stopMonitoringTimer()
|
||
stopCaffeinate()
|
||
}
|
||
}
|