cmux/Sources/AppDelegate.swift
2026-01-22 16:53:03 -08:00

111 lines
4.2 KiB
Swift

import AppKit
import UserNotifications
final class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate {
static var shared: AppDelegate?
weak var tabManager: TabManager?
weak var notificationStore: TerminalNotificationStore?
override init() {
super.init()
Self.shared = self
}
func applicationDidFinishLaunching(_ notification: Notification) {
configureUserNotifications()
}
func configure(tabManager: TabManager, notificationStore: TerminalNotificationStore) {
self.tabManager = tabManager
self.notificationStore = notificationStore
}
private func configureUserNotifications() {
let actions = [
UNNotificationAction(
identifier: TerminalNotificationStore.actionShowIdentifier,
title: "Show"
)
]
let category = UNNotificationCategory(
identifier: TerminalNotificationStore.categoryIdentifier,
actions: actions,
intentIdentifiers: [],
options: [.customDismissAction]
)
let center = UNUserNotificationCenter.current()
center.setNotificationCategories([category])
center.delegate = self
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void
) {
handleNotificationResponse(response)
completionHandler()
}
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
) {
let shouldPresent = shouldPresentNotification(notification)
let options: UNNotificationPresentationOptions = shouldPresent ? [.banner, .sound] : []
completionHandler(options)
}
private func handleNotificationResponse(_ response: UNNotificationResponse) {
guard let tabIdString = response.notification.request.content.userInfo["tabId"] as? String,
let tabId = UUID(uuidString: tabIdString) else {
return
}
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier, TerminalNotificationStore.actionShowIdentifier:
DispatchQueue.main.async {
if let notificationId = UUID(uuidString: response.notification.request.identifier) {
self.notificationStore?.markRead(id: notificationId)
} else if let notificationIdString = response.notification.request.content.userInfo["notificationId"] as? String,
let notificationId = UUID(uuidString: notificationIdString) {
self.notificationStore?.markRead(id: notificationId)
}
self.tabManager?.focusTab(tabId)
}
case UNNotificationDismissActionIdentifier:
DispatchQueue.main.async {
if let notificationId = UUID(uuidString: response.notification.request.identifier) {
self.notificationStore?.markRead(id: notificationId)
} else if let notificationIdString = response.notification.request.content.userInfo["notificationId"] as? String,
let notificationId = UUID(uuidString: notificationIdString) {
self.notificationStore?.markRead(id: notificationId)
}
}
default:
break
}
}
private func shouldPresentNotification(_ notification: UNNotification) -> Bool {
guard let tabManager else { return true }
guard let tabIdString = notification.request.content.userInfo["tabId"] as? String,
let tabId = UUID(uuidString: tabIdString) else {
return true
}
let isAppActive = NSApp.isActive
let isTabActive = tabManager.selectedTabId == tabId
let isKeyWindow = NSApp.keyWindow?.isKeyWindow ?? false
if isAppActive && isTabActive && isKeyWindow {
return false
}
return true
}
}