From 181cfa13505942f29a74014a552665449df5159e Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 27 Dec 2023 08:49:12 -0800 Subject: [PATCH] macos: notifications use surface UUID for lookups Fixes #1162 --- macos/Sources/AppDelegate.swift | 10 ++++++++++ macos/Sources/Ghostty/AppState.swift | 19 +++++++++++-------- macos/Sources/Ghostty/SurfaceView.swift | 11 +---------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 48dcc21ec..1b6e5bd29 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -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 diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 32f22da59..0f4570e5c 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -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 } diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 0ebcf5a15..47f5e542f 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -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(