feat: 添加开机自启动功能和用户指引界面

- 新增开机自启动功能,使用SMAppService实现
- 添加用户首次使用指引界面,包含功能介绍和权限说明
- 更新菜单栏选项,增加自启动开关和查看指南按钮
- 添加应用权限配置文件PowerBar.entitlements
- 创建README文档和.gitignore文件
This commit is contained in:
2025-12-27 18:39:01 +08:00
parent a928c050b9
commit 041a4bb911
5 changed files with 433 additions and 1 deletions

98
.gitignore vendored Normal file
View File

@@ -0,0 +1,98 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, too
## Build generated
build/
DerivedData/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Various settings
*.pbxproj
*.xccheckout
*.moved-aside
*.xcuserstate
*.xcscmblueprint
*.xccheckout
## Other
*.moved-aside
*.xcuserstate
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## CocoaPods
Pods/
Podfile.lock
## Carthage
Carthage/Build/
## fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
## Bundle artifact
*.jsbundle
## External dependencies
*.framework
*.a
*.dylib
## App Store submission
*.ipa
*.app.dSYM.zip
*.app.dSYM
## Local testing
*.xcodeproj/xcshareddata/xcschemes/
*.xcodeproj/project.xcworkspace/xcuserdata/
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Icon must be in root
!Icon?
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# PowerBar specific
PowerBar.xcarchive/
ExportedApp/
PowerBar.dmg
ExportOptions.plist

View File

@@ -1,6 +1,7 @@
import Foundation import Foundation
import Combine import Combine
import IOKit.pwr_mgt import IOKit.pwr_mgt
import ServiceManagement
// MARK: - App State Manager // MARK: - App State Manager
class AppState: ObservableObject { class AppState: ObservableObject {
@@ -12,6 +13,9 @@ class AppState: ObservableObject {
// //
@Published var isUserIdle: Bool = false @Published var isUserIdle: Bool = false
//
@Published var isLaunchAtLogin: Bool = false
// //
private var monitorTimer: Timer? private var monitorTimer: Timer?
private var caffeinateProcess: Process? // 使Process private var caffeinateProcess: Process? // 使Process
@@ -20,6 +24,12 @@ class AppState: ObservableObject {
private let idleThreshold: TimeInterval = 300 // 5 private let idleThreshold: TimeInterval = 300 // 5
private let checkInterval: TimeInterval = 10 // 10 private let checkInterval: TimeInterval = 10 // 10
//
init() {
//
checkLaunchAtLoginStatus()
}
// MARK: - Public Methods // MARK: - Public Methods
/// ///
@@ -153,6 +163,34 @@ class AppState: ObservableObject {
} }
} }
// 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: - Deinitializer // MARK: - Deinitializer
deinit { deinit {

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- 允许应用程序请求自动启动权限 -->
<key>com.apple.security.automation.apple-events</key>
<true/>
<!-- 允许应用程序访问IOKit服务 -->
<key>com.apple.security.iokit-user-client-class</key>
<array>
<string>IOPMPowerSource</string>
<string>IOHIDSystem</string>
</array>
<!-- 允许应用程序执行系统命令caffeinate -->
<key>com.apple.security.shell</key>
<true/>
<!-- 允许应用程序访问文件系统的某些部分 -->
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -4,6 +4,8 @@ import SwiftUI
struct PowerBarApp: App { struct PowerBarApp: App {
// App // App
@StateObject private var appState = AppState() @StateObject private var appState = AppState()
//
@AppStorage("hasShownGuide") private var hasShownGuide = false
var body: some Scene { var body: some Scene {
// 使 MenuBarExtra // 使 MenuBarExtra
@@ -23,7 +25,18 @@ struct PowerBarApp: App {
.foregroundColor(appState.isFunctionEnabled ? .green : .gray) .foregroundColor(appState.isFunctionEnabled ? .green : .gray)
} }
} }
// setting //
WindowGroup {
if !hasShownGuide {
GuideView()
.onDisappear {
hasShownGuide = true
}
}
}
.windowStyle(.hiddenTitleBar)
.defaultSize(width: 400, height: 400)
.windowResizability(.contentSize)
} }
} }
@@ -117,6 +130,39 @@ struct MenuContent: View {
Divider() Divider()
//
Toggle(isOn: Binding(
get: { appState.isLaunchAtLogin },
set: { _ in appState.toggleLaunchAtLogin() }
)) {
HStack {
Image(systemName: "power.on.circle")
.frame(width: 20)
Text("开机自动启动")
}
}
.toggleStyle(.checkbox)
.font(.caption)
.padding(4)
Divider()
//
Button("查看使用指南") {
//
// 使 AppStorage
UserDefaults.standard.set(false, forKey: "hasShownGuide")
//
// macOS
NSAlert.showAlert(title: "使用指南", message: "请重启应用以查看使用指南,或在下次启动时自动显示。")
}
.buttonStyle(.plain)
.font(.caption)
.foregroundColor(.secondary)
.padding(4)
Divider()
// 退 // 退
Button("退出 PowerBar") { Button("退出 PowerBar") {
NSApp.terminate(nil) NSApp.terminate(nil)
@@ -135,3 +181,171 @@ struct MenuContent: View {
) )
} }
} }
// NSAlert便
extension NSAlert {
static func showAlert(title: String, message: String) {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = message
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
//
struct GuideView: View {
var body: some View {
VStack(spacing: 20) {
//
VStack(spacing: 8) {
Image(systemName: "power")
.font(.system(size: 48))
.foregroundColor(.blue)
Text("欢迎使用 PowerBar")
.font(.title)
.fontWeight(.bold)
Text("智能睡眠管理工具")
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.top, 20)
//
ScrollView {
VStack(alignment: .leading, spacing: 16) {
//
Section {
HStack(spacing: 12) {
Image(systemName: "sun.max.fill")
.font(.title)
.foregroundColor(.green)
.frame(width: 40)
VStack(alignment: .leading) {
Text("智能睡眠管理")
.font(.headline)
Text("根据您的活动状态自动控制屏幕睡眠,活动时保持屏幕常亮,空闲时允许系统睡眠。")
.font(.body)
.foregroundColor(.secondary)
}
}
HStack(spacing: 12) {
Image(systemName: "power.on.circle")
.font(.title)
.foregroundColor(.orange)
.frame(width: 40)
VStack(alignment: .leading) {
Text("开机自动启动")
.font(.headline)
Text("可以选择在开机时自动启动应用,无需手动打开。")
.font(.body)
.foregroundColor(.secondary)
}
}
}
//
Section {
Text("所需权限")
.font(.title2)
.fontWeight(.bold)
VStack(alignment: .leading, spacing: 8) {
Text("1. **自动启动权限**:用于实现开机自动启动功能")
Text("2. **系统空闲时间访问**:用于检测您的活动状态")
Text("3. **系统命令执行**:用于运行 caffeinate 命令阻止系统睡眠")
}
.font(.body)
.foregroundColor(.secondary)
}
// 使
Section {
Text("使用指引")
.font(.title2)
.fontWeight(.bold)
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("1.")
.fontWeight(.bold)
.frame(width: 20, alignment: .leading)
Text("点击菜单栏中的太阳图标打开菜单")
}
HStack {
Text("2.")
.fontWeight(.bold)
.frame(width: 20, alignment: .leading)
Text("点击\"开启常亮\"按钮启动智能睡眠管理")
}
HStack {
Text("3.")
.fontWeight(.bold)
.frame(width: 20, alignment: .leading)
Text("应用会根据您的活动状态自动调整睡眠设置")
}
HStack {
Text("4.")
.fontWeight(.bold)
.frame(width: 20, alignment: .leading)
Text("可以选择\"开机自动启动\"方便长期使用")
}
}
.font(.body)
}
//
Section {
Text("状态说明")
.font(.title2)
.fontWeight(.bold)
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 12) {
Image(systemName: "sun.max.fill")
.foregroundColor(.green)
Text("绿色太阳:功能已开启,系统处于活动状态")
}
HStack(spacing: 12) {
Image(systemName: "sun.max.fill")
.foregroundColor(.orange)
Text("橙色太阳:功能已开启,系统处于空闲状态")
}
HStack(spacing: 12) {
Image(systemName: "sun.max")
.foregroundColor(.gray)
Text("灰色太阳:功能已关闭")
}
}
.font(.body)
}
}
.padding(.horizontal, 20)
}
//
Button(action: {
//
NSApp.keyWindow?.close()
}) {
Text("开始使用")
.font(.headline)
.foregroundColor(.white)
.padding(.vertical, 12)
.padding(.horizontal, 32)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.blue)
)
}
.padding(.bottom, 20)
}
.frame(width: 400, height: 500)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.white)
)
.shadow(radius: 10)
}
}

58
README.md Normal file
View File

@@ -0,0 +1,58 @@
# PowerBar
一个智能的macOS菜单栏应用根据用户活动状态自动管理系统睡眠。
## 功能特性
- 📊 **智能睡眠管理**:根据用户活动状态自动控制系统睡眠
- 🔍 **空闲检测**5分钟无操作自动视为空闲停止阻止睡眠
- 🎯 **清晰的状态指示**:通过菜单栏图标直观显示当前状态
- 🎨 **现代化UI**:简洁美观的菜单界面,易于操作
-**低资源消耗**:轻量级设计,几乎不占用系统资源
## 安装方法
### 方法1直接运行
1. 下载并解压应用包
2. 双击 `PowerBar.app` 即可运行
### 方法2通过DMG安装
1. 下载 `PowerBar.dmg` 文件
2. 双击打开DMG文件
3.`PowerBar.app` 拖放到 `Applications` 文件夹
4. 从Launchpad或Applications文件夹启动应用
## 使用说明
1. **启动应用**:双击应用图标,菜单栏会出现太阳图标
2. **查看状态**:点击太阳图标查看当前状态
3. **切换功能**:点击"开启常亮"或"关闭常亮"按钮切换功能
4. **退出应用**:点击"退出 PowerBar"退出应用
## 状态说明
- 🟢 **绿色太阳**:功能已开启,系统处于活动状态,显示屏受到保护
- 🟡 **橙色太阳**:功能已开启,系统处于空闲状态
-**灰色太阳**:功能已关闭
## 技术细节
- 基于SwiftUI开发
- 使用IOKit获取系统空闲时间
- 使用caffeinate命令管理系统睡眠
- 支持arm64和x86_64架构
## 系统要求
- macOS 13.0+ (Ventura)
- Apple Silicon 或 Intel 处理器
## 许可证
MIT License
## 作者
Leo12025