Compare commits

..

8 Commits

Author SHA1 Message Date
aef0f74616 refactor: 简化空闲状态检测逻辑,直接显示保护状态
移除空闲状态检测及相关UI显示,直接显示"保护中"状态
2025-12-27 22:01:35 +08:00
886b460d5e fix: 添加更多caffeinate参数以防止系统睡眠
添加-i和-s参数以全面防止系统睡眠,包括用户不活动和系统睡眠的情况
2025-12-27 21:52:39 +08:00
27e98a527f feat(assets): 添加应用图标资源文件
添加不同尺寸的Mac应用图标文件并更新Contents.json配置
2025-12-27 21:32:33 +08:00
230a2836a6 chore: 删除临时目录中的Applications符号链接 2025-12-27 20:21:17 +08:00
3514f450c9 refactor: 移除菜单内容中多余的过渡动画效果 2025-12-27 20:17:03 +08:00
6b645dedf0 refactor: 移除指引窗口的动画效果以简化代码 2025-12-27 20:15:27 +08:00
3cb14be4bc feat(UI): 添加动画效果和优化界面布局
为菜单内容和指引界面添加平滑的动画过渡效果
优化权限管理区域的布局和视觉层次
调整窗口打开/关闭时的渐变动画
2025-12-27 20:09:29 +08:00
be65e275b3 feat(权限): 添加权限检查和管理功能
添加权限检查功能,包括自动启动和系统命令执行权限
在菜单中添加打开系统设置和登录项管理的按钮
2025-12-27 19:57:52 +08:00
14 changed files with 154 additions and 88 deletions

View File

@@ -250,6 +250,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
@@ -259,6 +260,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -274,7 +276,6 @@
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
INFOPLIST_KEY_LSUIElement = YES;
};
name = Debug;
};
@@ -283,6 +284,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
@@ -292,6 +294,7 @@
ENABLE_PREVIEWS = YES;
ENABLE_USER_SELECTED_FILES = readonly;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_LSUIElement = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
@@ -307,7 +310,6 @@
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
INFOPLIST_KEY_LSUIElement = YES;
};
name = Release;
};

View File

@@ -2,6 +2,7 @@ import Foundation
import Combine
import IOKit.pwr_mgt
import ServiceManagement
import AppKit
// MARK: - App State Manager
class AppState: ObservableObject {
@@ -10,20 +11,12 @@ class AppState: ObservableObject {
//
@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() {
//
@@ -36,52 +29,11 @@ class AppState: ObservableObject {
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 {
//
// caffeinate
startCaffeinate()
}
} else {
// caffeinate
stopCaffeinate()
}
}
@@ -124,7 +76,7 @@ class AppState: ObservableObject {
// caffeinate
caffeinateProcess = Process()
caffeinateProcess?.executableURL = URL(fileURLWithPath: "/usr/bin/caffeinate")
caffeinateProcess?.arguments = ["-d"] // -d
caffeinateProcess?.arguments = ["-i", "-s", "-d"] // -i -s -d
//
let outputPipe = Pipe()
@@ -154,14 +106,7 @@ class AppState: ObservableObject {
print("已重置 caffeinate 进程引用")
}
// MARK: - UI State Update
/// 线
private func updateUserIdleStatus(_ isIdle: Bool) {
DispatchQueue.main.async {
self.isUserIdle = isIdle
}
}
// MARK: - Launch at Login Management
@@ -191,11 +136,66 @@ class AppState: ObservableObject {
}
}
// 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()
}
}

View File

@@ -1,51 +1,61 @@
{
"images" : [
{
"filename" : "icon_16x16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon_16x16@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon_32x32.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon_32x32@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"filename" : "icon_128x128.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"filename" : "icon_128x128@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"filename" : "icon_256x256.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"filename" : "icon_256x256@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"filename" : "icon_512x512.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon_512x512@2x.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

View File

@@ -55,12 +55,15 @@ struct MenuContent: View {
//
Button(action: {
//
withAnimation(.easeInOut(duration: 0.3)) {
appState.toggleFunction()
}
}) {
HStack(spacing: 12) {
Image(systemName: appState.isFunctionEnabled ? "stop.circle.fill" : "play.circle.fill")
.foregroundColor(appState.isFunctionEnabled ? .red : .green)
.font(.title2)
.transition(.scale(scale: 0.8, anchor: .center))
VStack(alignment: .leading, spacing: 2) {
Text(appState.isFunctionEnabled ? "关闭常亮" : "开启常亮")
.font(.body)
@@ -74,6 +77,7 @@ struct MenuContent: View {
.background(
RoundedRectangle(cornerRadius: 8)
.fill(appState.isFunctionEnabled ? Color.red.opacity(0.1) : Color.green.opacity(0.1))
.transition(.opacity)
)
}
.buttonStyle(.plain)
@@ -89,17 +93,10 @@ struct MenuContent: View {
if appState.isFunctionEnabled {
HStack(spacing: 6) {
Circle()
.fill(appState.isUserIdle ? Color.orange : Color.green)
.fill(Color.green)
.frame(width: 8, height: 8)
Text(appState.isUserIdle ? "空闲" : "活动")
Text("保护")
.font(.body)
// caffeinate
if !appState.isUserIdle {
Text("(保护中)")
.font(.caption2)
.foregroundColor(.secondary)
}
}
} else {
HStack(spacing: 6) {
@@ -112,6 +109,7 @@ struct MenuContent: View {
}
Spacer()
}
}
.padding(8)
.background(
@@ -121,6 +119,68 @@ struct MenuContent: View {
Divider()
//
VStack(alignment: .leading, spacing: 8) {
Text("权限管理")
.font(.caption)
.foregroundColor(.secondary)
//
Button(action: {
//
let settingsUrl = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation")!
if NSWorkspace.shared.open(settingsUrl) {
print("已打开系统设置自动化页面")
} else {
//
let fallbackUrl = URL(string: "x-apple.systempreferences:com.apple.preference.security")!
NSWorkspace.shared.open(fallbackUrl)
}
}) {
HStack {
Image(systemName: "gearshape.fill")
.frame(width: 20)
.foregroundColor(.blue)
Text("打开系统设置")
.font(.caption)
.foregroundColor(.blue)
}
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.blue.opacity(0.05))
)
}
.buttonStyle(.plain)
//
Button(action: {
let settingsUrl = URL(string: "x-apple.systempreferences:com.apple.preference.usersandgroups?LoginItems")!
NSWorkspace.shared.open(settingsUrl)
}) {
HStack {
Image(systemName: "person.2.fill")
.frame(width: 20)
.foregroundColor(.blue)
Text("管理登录项")
.font(.caption)
.foregroundColor(.blue)
}
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color.blue.opacity(0.05))
)
}
.buttonStyle(.plain)
}
.padding(8)
Divider()
//
Toggle(isOn: Binding(
get: { appState.isLaunchAtLogin },
@@ -134,7 +194,7 @@ struct MenuContent: View {
}
.toggleStyle(.checkbox)
.font(.caption)
.padding(4)
.padding(8)
Divider()
@@ -146,7 +206,7 @@ struct MenuContent: View {
.buttonStyle(.plain)
.font(.caption)
.foregroundColor(.secondary)
.padding(4)
.padding(8)
Divider()
@@ -157,7 +217,7 @@ struct MenuContent: View {
.buttonStyle(.plain)
.font(.caption)
.foregroundColor(.secondary)
.padding(4)
.padding(8)
}
.padding(12)
.frame(minWidth: 280)
@@ -188,6 +248,8 @@ struct MenuContent: View {
window.title = "PowerBar 使用指南"
window.contentView = NSHostingView(rootView: GuideView())
window.center()
//
window.makeKeyAndOrderFront(nil)
//
@@ -195,16 +257,6 @@ struct MenuContent: View {
//
guideWindow = window
//
NotificationCenter.default.addObserver(
forName: NSWindow.willCloseNotification,
object: window,
queue: nil
) { _ in
//
guideWindow = nil
}
}
}
@@ -257,7 +309,7 @@ struct GuideView: View {
}
HStack(spacing: 12) {
Image(systemName: "power.on.circle")
Image(systemName: "power.circle")
.font(.title)
.foregroundColor(.orange)
.frame(width: 40)
@@ -364,6 +416,8 @@ struct GuideView: View {
RoundedRectangle(cornerRadius: 8)
.fill(.blue)
)
.transition(.scale(scale: 0.9, anchor: .center))
.animation(.easeInOut(duration: 0.2), value: UUID())
}
.padding(.bottom, 20)
}