feat(通知): 添加显示器连接状态检测和通知功能

当检测到外部显示器连接时自动启用保持亮屏功能
当外部显示器断开时发送通知询问是否关闭功能
添加通知代理处理用户响应
This commit is contained in:
2025-12-27 22:42:43 +08:00
parent aef0f74616
commit b133d8a4e9
3 changed files with 148 additions and 0 deletions

View File

@@ -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()
}
}

View File

@@ -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
View File

@@ -0,0 +1 @@
/Applications