Merge pull request #1165 from mitchellh/dangling-notification

macos: notifications use surface UUID for lookups
This commit is contained in:
Mitchell Hashimoto
2023-12-27 09:02:43 -08:00
committed by GitHub
3 changed files with 22 additions and 18 deletions

View File

@ -332,6 +332,16 @@ class AppDelegate: NSObject,
//MARK: - GhosttyAppStateDelegate
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView? {
for c in terminalManager.windows {
if let v = c.controller.surfaceTree?.findUUID(uuid: uuid) {
return v
}
}
return nil
}
func configDidReload(_ state: Ghostty.AppState) {
// Depending on the "window-save-state" setting we have to set the NSQuitAlwaysKeepsWindows
// configuration. This is the only way to carefully control whether macOS invokes the

View File

@ -5,6 +5,10 @@ import GhosttyKit
protocol GhosttyAppStateDelegate: AnyObject {
/// Called when the configuration did finish reloading.
func configDidReload(_ state: Ghostty.AppState)
/// Called when a callback needs access to a specific surface. This should return nil
/// when the surface is no longer valid.
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView?
}
extension Ghostty {
@ -608,9 +612,9 @@ extension Ghostty {
/// Handle a received user notification. This is called when a user notification is clicked or dismissed by the user
func handleUserNotification(response: UNNotificationResponse) {
let userInfo = response.notification.request.content.userInfo
guard let address = userInfo["address"] as? Int else { return }
guard let userdata = UnsafeMutableRawPointer(bitPattern: address) else { return }
let surface = Ghostty.AppState.surfaceUserdata(from: userdata)
guard let uuidString = userInfo["surface"] as? String,
let uuid = UUID(uuidString: uuidString),
let surface = delegate?.findSurface(forUUID: uuid) else { return }
switch (response.actionIdentifier) {
case UNNotificationDefaultActionIdentifier, Ghostty.userNotificationActionShow:
@ -627,11 +631,10 @@ extension Ghostty {
/// Determine if a given notification should be presented to the user when Ghostty is running in the foreground.
func shouldPresentNotification(notification: UNNotification) -> Bool {
let userInfo = notification.request.content.userInfo
guard let address = userInfo["address"] as? Int else { return false }
guard let userdata = UnsafeMutableRawPointer(bitPattern: address) else { return false }
let surface = Ghostty.AppState.surfaceUserdata(from: userdata)
guard let window = surface.window else { return false }
guard let uuidString = userInfo["surface"] as? String,
let uuid = UUID(uuidString: uuidString),
let surface = delegate?.findSurface(forUUID: uuid),
let window = surface.window else { return false }
return !window.isKeyWindow || !surface.focused
}

View File

@ -1145,16 +1145,7 @@ extension Ghostty {
content.body = body
content.sound = UNNotificationSound.default
content.categoryIdentifier = Ghostty.userNotificationCategory
// The userInfo must conform to NSSecureCoding, which SurfaceView
// does not. So instead we pass an integer representation of the
// SurfaceView's address, which is reconstructed back into a
// SurfaceView if the notification is clicked. This is safe to do
// so long as the SurfaceView removes all of its notifications when
// it closes so that there are no dangling pointers.
content.userInfo = [
"address": Int(bitPattern: Unmanaged.passUnretained(self).toOpaque()),
]
content.userInfo = ["surface": self.uuid.uuidString]
let uuid = UUID().uuidString
let request = UNNotificationRequest(