feat(通知): 添加显示器连接状态检测和通知功能
当检测到外部显示器连接时自动启用保持亮屏功能 当外部显示器断开时发送通知询问是否关闭功能 添加通知代理处理用户响应
This commit is contained in:
@@ -3,6 +3,7 @@ import Combine
|
||||
import IOKit.pwr_mgt
|
||||
import ServiceManagement
|
||||
import AppKit
|
||||
import UserNotifications
|
||||
|
||||
// MARK: - App State Manager
|
||||
class AppState: ObservableObject {
|
||||
@@ -16,11 +17,20 @@ class AppState: ObservableObject {
|
||||
|
||||
// 内部属性
|
||||
private var caffeinateProcess: Process? // 直接使用Process对象,但简化实现
|
||||
private var displayCountObserver: AnyCancellable? // 监听显示器数量变化
|
||||
private var wasAutomaticallyEnabled: Bool = false // 记录是否是自动启用的
|
||||
private var currentDisplayCount: Int = NSScreen.screens.count // 当前显示器数量
|
||||
|
||||
// 初始化方法
|
||||
init() {
|
||||
// 检查并设置初始的自动启动状态
|
||||
checkLaunchAtLoginStatus()
|
||||
|
||||
// 请求通知权限
|
||||
requestNotificationPermission()
|
||||
|
||||
// 设置显示器数量变化监听
|
||||
setupDisplayCountObserver()
|
||||
}
|
||||
|
||||
// MARK: - Public Methods
|
||||
@@ -192,10 +202,122 @@ class AppState: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Display Monitoring and Auto-enable
|
||||
|
||||
/// 请求通知权限
|
||||
private func requestNotificationPermission() {
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
||||
if granted {
|
||||
print("通知权限已授予")
|
||||
} else if let error = error {
|
||||
print("请求通知权限失败: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置显示器数量变化监听
|
||||
private func setupDisplayCountObserver() {
|
||||
// 使用 Combine 监听 NSScreen.screens.count 的变化
|
||||
// 这里使用定时器定期检查,因为没有直接的通知可以监听
|
||||
displayCountObserver = Timer.publish(every: 5, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
.sink { [weak self] _ in
|
||||
self?.checkDisplayCount()
|
||||
}
|
||||
}
|
||||
|
||||
/// 检查显示器数量变化
|
||||
private func checkDisplayCount() {
|
||||
let newDisplayCount = NSScreen.screens.count
|
||||
|
||||
// 如果显示器数量变化
|
||||
if newDisplayCount != currentDisplayCount {
|
||||
if newDisplayCount > 1 {
|
||||
// 外部显示器连接
|
||||
handleExternalDisplayConnected()
|
||||
} else {
|
||||
// 外部显示器断开
|
||||
handleExternalDisplayDisconnected()
|
||||
}
|
||||
currentDisplayCount = newDisplayCount
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理外部显示器连接
|
||||
private func handleExternalDisplayConnected() {
|
||||
if !isFunctionEnabled {
|
||||
// 自动启用保持亮屏
|
||||
wasAutomaticallyEnabled = true
|
||||
toggleFunction()
|
||||
|
||||
// 发送通知
|
||||
sendNotification(title: "PowerBar", body: "检测到外部显示器,已自动打开保持亮屏功能")
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理外部显示器断开
|
||||
private func handleExternalDisplayDisconnected() {
|
||||
if wasAutomaticallyEnabled && isFunctionEnabled {
|
||||
// 发送通知询问是否关闭保持亮屏
|
||||
sendNotificationWithAction(title: "PowerBar", body: "外部显示器已断开,是否关闭保持亮屏功能?")
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送普通通知
|
||||
private func sendNotification(title: String, body: String) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = UNNotificationSound.default
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error = error {
|
||||
print("发送通知失败: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 发送带操作的通知
|
||||
private func sendNotificationWithAction(title: String, body: String) {
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = title
|
||||
content.body = body
|
||||
content.sound = UNNotificationSound.default
|
||||
content.categoryIdentifier = "displayDisconnected"
|
||||
|
||||
// 注册通知类别和操作
|
||||
let cancelAction = UNNotificationAction(identifier: "cancel", title: "取消", options: [])
|
||||
let disableAction = UNNotificationAction(identifier: "disable", title: "关闭", options: [])
|
||||
let category = UNNotificationCategory(identifier: "displayDisconnected", actions: [disableAction, cancelAction], intentIdentifiers: [])
|
||||
UNUserNotificationCenter.current().setNotificationCategories([category])
|
||||
|
||||
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error = error {
|
||||
print("发送通知失败: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理通知操作
|
||||
func handleNotificationResponse(identifier: String) {
|
||||
if identifier == "disable" {
|
||||
// 用户选择关闭保持亮屏
|
||||
wasAutomaticallyEnabled = false
|
||||
toggleFunction()
|
||||
} else if identifier == "cancel" {
|
||||
// 用户选择取消,保持当前状态
|
||||
wasAutomaticallyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Deinitializer
|
||||
|
||||
deinit {
|
||||
// 确保 App 退出时清理资源
|
||||
stopCaffeinate()
|
||||
displayCountObserver?.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
struct PowerBarApp: App {
|
||||
@@ -26,6 +27,30 @@ struct PowerBarApp: App {
|
||||
.foregroundColor(appState.isFunctionEnabled ? .green : .gray)
|
||||
}
|
||||
}
|
||||
.onChange(of: ScenePhase.active) {
|
||||
// 当应用变为活动状态时,设置通知中心代理
|
||||
UNUserNotificationCenter.current().delegate = NotificationDelegate.shared
|
||||
NotificationDelegate.shared.appState = appState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 通知代理,处理通知响应
|
||||
class NotificationDelegate: NSObject, UNUserNotificationCenterDelegate {
|
||||
static let shared = NotificationDelegate()
|
||||
weak var appState: AppState?
|
||||
|
||||
private override init() { super.init() }
|
||||
|
||||
// 当通知在前台收到时调用
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
return [.banner, .sound]
|
||||
}
|
||||
|
||||
// 当用户点击通知操作时调用
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
|
||||
let actionIdentifier = response.actionIdentifier
|
||||
appState?.handleNotificationResponse(identifier: actionIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1
temp_dmg/Applications
Symbolic link
1
temp_dmg/Applications
Symbolic link
@@ -0,0 +1 @@
|
||||
/Applications
|
||||
Reference in New Issue
Block a user