259 lines
8.4 KiB
Swift
259 lines
8.4 KiB
Swift
import Foundation
|
||
import Combine
|
||
import IOKit.pwr_mgt
|
||
import ServiceManagement
|
||
import AppKit
|
||
|
||
// MARK: - App State Manager
|
||
class AppState: ObservableObject {
|
||
// MARK: - Published Properties (UI 会观察这些属性)
|
||
|
||
// 功能的总开关
|
||
@Published var isFunctionEnabled: Bool = false
|
||
|
||
// 用户当前是否空闲
|
||
@Published var isUserIdle: Bool = false
|
||
|
||
// 自动启动设置
|
||
@Published var isLaunchAtLogin: Bool = false
|
||
|
||
// 内部属性
|
||
private var monitorTimer: Timer?
|
||
private var caffeinateProcess: Process? // 直接使用Process对象,但简化实现
|
||
|
||
// 配置参数
|
||
private let idleThreshold: TimeInterval = 300 // 5分钟无操作视为空闲
|
||
private let checkInterval: TimeInterval = 10 // 每10秒检查一次
|
||
|
||
// 初始化方法
|
||
init() {
|
||
// 检查并设置初始的自动启动状态
|
||
checkLaunchAtLoginStatus()
|
||
}
|
||
|
||
// 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
|
||
|
||
// 获取 IO Master Port
|
||
let kr = IOMainPort(0, &onPort)
|
||
guard kr == KERN_SUCCESS else { return 0 }
|
||
|
||
// 查找 HID 系统服务,这是正确的获取 HIDIdleTime 的服务
|
||
let hidSystem = IOServiceGetMatchingService(onPort, IOServiceMatching("IOHIDSystem"))
|
||
guard hidSystem != 0 else { return 0 }
|
||
|
||
// 尝试获取空闲时间属性
|
||
let result = IORegistryEntryCreateCFProperty(hidSystem, "HIDIdleTime" as CFString, kCFAllocatorDefault, 0)
|
||
IOObjectRelease(hidSystem)
|
||
|
||
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 阻止睡眠...")
|
||
|
||
// 简化实现,直接启动 caffeinate 进程
|
||
caffeinateProcess = Process()
|
||
caffeinateProcess?.executableURL = URL(fileURLWithPath: "/usr/bin/caffeinate")
|
||
caffeinateProcess?.arguments = ["-d"] // -d 指示系统不要关闭显示屏
|
||
|
||
// 配置输出,避免产生不必要的输出
|
||
let outputPipe = Pipe()
|
||
caffeinateProcess?.standardOutput = outputPipe
|
||
caffeinateProcess?.standardError = outputPipe
|
||
caffeinateProcess?.standardInput = Pipe()
|
||
|
||
do {
|
||
try caffeinateProcess?.run()
|
||
print("成功启动 caffeinate 进程")
|
||
} catch {
|
||
print("启动 caffeinate 失败: \(error)")
|
||
caffeinateProcess = nil
|
||
}
|
||
}
|
||
|
||
private func stopCaffeinate() {
|
||
guard let process = caffeinateProcess else { return }
|
||
|
||
print("用户空闲或功能关闭,停止 caffeinate 进程")
|
||
|
||
// 简化实现,直接终止进程
|
||
process.terminate()
|
||
|
||
// 不等待进程退出,避免阻塞
|
||
caffeinateProcess = nil
|
||
print("已重置 caffeinate 进程引用")
|
||
}
|
||
|
||
// MARK: - UI State Update
|
||
|
||
/// 在主线程更新用户空闲状态
|
||
private func updateUserIdleStatus(_ isIdle: Bool) {
|
||
DispatchQueue.main.async {
|
||
self.isUserIdle = isIdle
|
||
}
|
||
}
|
||
|
||
// MARK: - Launch at Login Management
|
||
|
||
/// 检查当前的自动启动状态
|
||
private func checkLaunchAtLoginStatus() {
|
||
// 使用 Bundle.main.bundleIdentifier 获取应用的 bundle ID
|
||
guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return }
|
||
isLaunchAtLogin = SMAppService.mainApp.status == .enabled
|
||
}
|
||
|
||
/// 切换自动启动状态
|
||
func toggleLaunchAtLogin() {
|
||
isLaunchAtLogin.toggle()
|
||
|
||
do {
|
||
if isLaunchAtLogin {
|
||
try SMAppService.mainApp.register()
|
||
print("已设置开机自动启动")
|
||
} else {
|
||
try SMAppService.mainApp.unregister()
|
||
print("已取消开机自动启动")
|
||
}
|
||
} catch {
|
||
print("设置开机自动启动失败: \(error)")
|
||
// 恢复原来的状态
|
||
isLaunchAtLogin.toggle()
|
||
}
|
||
}
|
||
|
||
// MARK: - Permission Management
|
||
|
||
/// 检查应用程序所需的权限
|
||
func checkPermissions() {
|
||
print("检查应用程序权限...")
|
||
|
||
// 检查自动启动权限状态
|
||
checkLaunchAtLoginStatus()
|
||
print("自动启动权限: \(isLaunchAtLogin ? "已启用" : "已禁用")")
|
||
|
||
// 检查是否可以执行系统命令
|
||
let canExecuteSystemCommands = checkSystemCommandPermission()
|
||
print("系统命令执行权限: \(canExecuteSystemCommands ? "已允许" : "已拒绝")")
|
||
}
|
||
|
||
/// 检查是否可以执行系统命令
|
||
private func checkSystemCommandPermission() -> Bool {
|
||
// 尝试运行一个简单的系统命令来检查权限
|
||
let process = Process()
|
||
process.executableURL = URL(fileURLWithPath: "/bin/echo")
|
||
process.arguments = ["test"]
|
||
|
||
do {
|
||
try process.run()
|
||
process.waitUntilExit()
|
||
return process.terminationStatus == 0
|
||
} catch {
|
||
print("执行系统命令失败: \(error)")
|
||
return false
|
||
}
|
||
}
|
||
|
||
/// 打开系统设置的隐私与安全性页面
|
||
func openSecuritySettings() {
|
||
print("打开系统设置的隐私与安全性页面...")
|
||
|
||
let settingsUrl = URL(string: "x-apple.systempreferences:com.apple.preference.security")!
|
||
if NSWorkspace.shared.open(settingsUrl) {
|
||
print("已打开系统设置页面")
|
||
} else {
|
||
print("打开系统设置页面失败")
|
||
}
|
||
}
|
||
|
||
/// 打开系统设置的登录项页面
|
||
func openLoginItemsSettings() {
|
||
print("打开系统设置的登录项页面...")
|
||
|
||
let settingsUrl = URL(string: "x-apple.systempreferences:com.apple.preference.usersandgroups?LoginItems")!
|
||
if NSWorkspace.shared.open(settingsUrl) {
|
||
print("已打开登录项设置页面")
|
||
} else {
|
||
print("打开登录项设置页面失败")
|
||
}
|
||
}
|
||
|
||
// MARK: - Deinitializer
|
||
|
||
deinit {
|
||
// 确保 App 退出时清理资源
|
||
stopMonitoringTimer()
|
||
stopCaffeinate()
|
||
}
|
||
}
|