macos: further simplication of AppDelegate bell state

This commit is contained in:
Mitchell Hashimoto
2026-02-26 09:50:16 -08:00
parent 79ca4daea6
commit 3aca722415
2 changed files with 21 additions and 23 deletions

View File

@@ -151,9 +151,6 @@ class AppDelegate: NSObject,
/// Signals
private var signals: [DispatchSourceSignal] = []
/// Current bell state keyed by terminal controller identity.
private var windowBellStates: [ObjectIdentifier: Bool] = [:]
/// Cached permission state for dock badges.
private var canShowDockBadgeForBell: Bool = false
@@ -234,12 +231,6 @@ class AppDelegate: NSObject,
name: NSWindow.didBecomeKeyNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(windowWillClose),
name: NSWindow.willCloseNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(quickTerminalDidChangeVisibility),
@@ -773,14 +764,6 @@ class AppDelegate: NSObject,
syncFloatOnTopMenu(notification.object as? NSWindow)
}
@objc private func windowWillClose(_ notification: Notification) {
guard let window = notification.object as? NSWindow,
let controller = window.windowController as? BaseTerminalController else { return }
windowBellStates[ObjectIdentifier(controller)] = nil
syncDockBadgeToTrackedBellState()
}
@objc private func quickTerminalDidChangeVisibility(_ notification: Notification) {
guard let quickController = notification.object as? QuickTerminalController else { return }
self.menuQuickTerminal?.state = if quickController.visible { .on } else { .off }
@@ -813,15 +796,14 @@ class AppDelegate: NSObject,
}
@objc private func terminalWindowHasBell(_ notification: Notification) {
guard let controller = notification.object as? BaseTerminalController,
let hasBell = notification.userInfo?[Notification.Name.terminalWindowHasBellKey] as? Bool else { return }
windowBellStates[ObjectIdentifier(controller)] = hasBell
guard notification.object is BaseTerminalController else { return }
syncDockBadgeToTrackedBellState()
}
private func syncDockBadgeToTrackedBellState() {
let anyBell = windowBellStates.values.contains(true)
let anyBell = NSApp.windows
.compactMap { $0.windowController as? BaseTerminalController }
.contains { $0.bell }
let wantsBadge = ghostty.config.bellFeatures.contains(.attention) && anyBell
if wantsBadge && !canShowDockBadgeForBell && !hasRequestedDockBadgeAuthorization {

View File

@@ -51,6 +51,9 @@ class BaseTerminalController: NSWindowController,
/// Set if the terminal view should show the update overlay.
@Published var updateOverlayIsVisible: Bool = false
/// True when any surface in this controller currently has an active bell.
@Published private(set) var bell: Bool = false
/// Whether the terminal surface should focus when the mouse is over it.
var focusFollowsMouse: Bool {
self.derivedConfig.focusFollowsMouse
@@ -137,7 +140,7 @@ class BaseTerminalController: NSWindowController,
// Initialize our initial surface.
guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") }
self.surfaceTree = tree ?? .init(view: Ghostty.SurfaceView(ghostty_app, baseConfig: base))
// Setup our bell state for the window
setupBellNotificationPublisher()
@@ -1206,6 +1209,17 @@ class BaseTerminalController: NSWindowController,
func windowWillClose(_ notification: Notification) {
guard let window else { return }
// Emit a final bell-state transition so any observers can clear state
// without separately tracking NSWindow lifecycle events.
if bell {
bell = false
NotificationCenter.default.post(
name: .terminalWindowBellDidChangeNotification,
object: self,
userInfo: [Notification.Name.terminalWindowHasBellKey: false]
)
}
// I don't know if this is required anymore. We previously had a ref cycle between
// the view and the window so we had to nil this out to break it but I think this
// may now be resolved. We should verify that no memory leaks and we can remove this.
@@ -1486,8 +1500,10 @@ extension BaseTerminalController {
bellStateCancellable = surfaceValuesPublisher(valueKeyPath: \.bell, publisherKeyPath: \.$bell)
.map { $0.values.contains(true) }
.removeDuplicates()
.receive(on: DispatchQueue.main)
.sink { [weak self] hasBell in
guard let self else { return }
bell = hasBell
NotificationCenter.default.post(
name: .terminalWindowBellDidChangeNotification,
object: self,