feat: 实现状态栏应用和防睡眠功能

添加 AppState 类管理应用状态和防睡眠逻辑
将主应用改为状态栏应用并添加交互菜单
使用 IOKit 检测系统空闲时间并控制 caffeinate 进程
This commit is contained in:
2025-12-27 18:17:16 +08:00
parent 2701634769
commit 8eb0086779
2 changed files with 224 additions and 9 deletions

View File

@@ -0,0 +1,150 @@
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()
}
}

View File

@@ -1,17 +1,82 @@
//
// PowerBarApp.swift
// PowerBar
//
// Created by Leo on 2025/12/27.
//
import SwiftUI import SwiftUI
@main @main
struct PowerBarApp: App { struct PowerBarApp: App {
// App
@StateObject private var appState = AppState()
var body: some Scene { var body: some Scene {
WindowGroup { // 使 MenuBarExtra
ContentView() // "PowerBar" fallback
//
MenuBarExtra {
//
MenuContent()
.environmentObject(appState)
} label: {
//
// appState
Label {
Text("PowerBar")
} icon: {
Image(systemName: appState.isFunctionEnabled ? "sun.max.fill" : "sun.max")
.foregroundColor(appState.isFunctionEnabled ? .green : .gray)
}
} }
// setting
}
}
//
struct MenuContent: View {
@EnvironmentObject var appState: AppState
var body: some View {
VStack(alignment: .leading, spacing: 10) {
//
Button(action: {
//
appState.toggleFunction()
}) {
HStack {
Image(systemName: appState.isFunctionEnabled ? "stop.circle.fill" : "play.circle.fill")
.foregroundColor(appState.isFunctionEnabled ? .red : .green)
.frame(width: 20)
Text(appState.isFunctionEnabled ? "关闭常亮" : "开启常亮")
}
}
Divider()
//
HStack {
Text("当前状态:")
.foregroundColor(.secondary)
Spacer()
if appState.isFunctionEnabled {
Text(appState.isUserIdle ? "空闲" : "活动中")
.foregroundColor(appState.isUserIdle ? .orange : .green)
// caffeinate
if !appState.isUserIdle {
Text(" (保护中)")
.foregroundColor(.green)
}
} else {
Text("已关闭")
.foregroundColor(.gray)
}
}
.font(.caption)
Divider()
// 退
Button("退出 PowerBar") {
NSApp.terminate(nil)
}
}
.padding(8)
.frame(minWidth: 220)
} }
} }