macOS: Add dock badge notification for bell events (#7118)

Resolves #7108

This PR adds visual notification badges to the Ghostty dock icon when
bell events are triggered while the application is in the background.
This complements the existing dock bounce notification, making it easier
for users to notice when a terminal needs attention.


https://github.com/user-attachments/assets/b54c881f-fea8-4085-8614-432d9e5847b9
pull/7171/head
Mitchell Hashimoto 2025-04-22 08:39:49 -07:00 committed by GitHub
commit ce987ba56d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 53 additions and 2 deletions

View File

@ -199,6 +199,7 @@ class AppDelegate: NSObject,
]
let center = UNUserNotificationCenter.current()
center.setNotificationCategories([
UNNotificationCategory(
identifier: Ghostty.userNotificationCategory,
@ -231,6 +232,9 @@ class AppDelegate: NSObject,
// If we're back manually then clear the hidden state because macOS handles it.
self.hiddenState = nil
// Clear the dock badge when the app becomes active
self.setDockBadge(nil)
// First launch stuff
if (!applicationHasBecomeActive) {
applicationHasBecomeActive = true
@ -511,6 +515,53 @@ class AppDelegate: NSObject,
@objc private func ghosttyBellDidRing(_ notification: Notification) {
// Bounce the dock icon if we're not focused.
NSApp.requestUserAttention(.informationalRequest)
// Handle setting the dock badge based on permissions
ghosttyUpdateBadgeForBell()
}
private func ghosttyUpdateBadgeForBell() {
let center = UNUserNotificationCenter.current()
center.getNotificationSettings { settings in
switch settings.authorizationStatus {
case .authorized:
// Already authorized, check badge setting and set if enabled
if settings.badgeSetting == .enabled {
DispatchQueue.main.async {
self.setDockBadge()
}
}
case .notDetermined:
// Not determined yet, request authorization for badge
center.requestAuthorization(options: [.badge]) { granted, error in
if let error = error {
Self.logger.warning("Error requesting badge authorization: \(error)")
return
}
if granted {
// Permission granted, set the badge
DispatchQueue.main.async {
self.setDockBadge()
}
}
}
case .denied, .provisional, .ephemeral:
// In these known non-authorized states, do not attempt to set the badge.
break
@unknown default:
// Handle future unknown states by doing nothing.
break
}
}
}
private func setDockBadge(_ label: String? = "") {
NSApp.dockTile.badgeLabel = label
NSApp.dockTile.display()
}
private func ghosttyConfigDidChange(config: Ghostty.Config) {