diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 98e3b87f9..62384586a 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -1,5 +1,6 @@ import Cocoa import SwiftUI +import Combine import GhosttyKit /// A base class for windows that can contain Ghostty windows. This base class implements @@ -71,6 +72,9 @@ class BaseTerminalController: NSWindowController, /// The configuration derived from the Ghostty config so we don't need to rely on references. private var derivedConfig: DerivedConfig + /// The cancellables related to our focused surface. + private var focusedSurfaceCancellables: Set = [] + struct SavedFrame { let window: NSRect let screen: NSRect @@ -286,7 +290,26 @@ class BaseTerminalController: NSWindowController, func surfaceTreeDidChange() {} func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) { + let lastFocusedSurface = focusedSurface focusedSurface = to + + // Important to cancel any prior subscriptions + focusedSurfaceCancellables = [] + + // Setup our title listener. If we have a focused surface we always use that. + // Otherwise, we try to use our last focused surface. In either case, we only + // want to care if the surface is in the tree so we don't listen to titles of + // closed surfaces. + if let titleSurface = focusedSurface ?? lastFocusedSurface, + surfaceTree?.contains(view: titleSurface) ?? false { + // If we have a surface, we want to listen for title changes. + titleSurface.$title + .sink { [weak self] in self?.titleDidChange(to: $0) } + .store(in: &focusedSurfaceCancellables) + } else { + // There is no surface to listen to titles for. + titleDidChange(to: "👻") + } } func titleDidChange(to: String) { diff --git a/macos/Sources/Features/Terminal/TerminalView.swift b/macos/Sources/Features/Terminal/TerminalView.swift index 1178c75a5..7caceb071 100644 --- a/macos/Sources/Features/Terminal/TerminalView.swift +++ b/macos/Sources/Features/Terminal/TerminalView.swift @@ -8,9 +8,6 @@ protocol TerminalViewDelegate: AnyObject { /// Called when the currently focused surface changed. This can be nil. func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) - /// The title of the terminal should change. - func titleDidChange(to: String) - /// The URL of the pwd should change. func pwdDidChange(to: URL?) @@ -59,19 +56,10 @@ struct TerminalView: View { // Various state values sent back up from the currently focused terminals. @FocusedValue(\.ghosttySurfaceView) private var focusedSurface - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle @FocusedValue(\.ghosttySurfacePwd) private var surfacePwd @FocusedValue(\.ghosttySurfaceZoomed) private var zoomedSplit @FocusedValue(\.ghosttySurfaceCellSize) private var cellSize - // The title for our window - private var title: String { - if let surfaceTitle, !surfaceTitle.isEmpty { - return surfaceTitle - } - return "👻" - } - // The pwd of the focused surface as a URL private var pwdURL: URL? { guard let surfacePwd, surfacePwd != "" else { return nil } @@ -105,9 +93,6 @@ struct TerminalView: View { self.delegate?.focusedSurfaceDidChange(to: newValue) } } - .onChange(of: title) { newValue in - self.delegate?.titleDidChange(to: newValue) - } .onChange(of: pwdURL) { newValue in self.delegate?.pwdDidChange(to: newValue) } diff --git a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift index 127c925e1..3e942d774 100644 --- a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift +++ b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift @@ -45,8 +45,6 @@ extension Ghostty { /// this one. @Binding var zoomedSurface: SurfaceView? - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? - var body: some View { let center = NotificationCenter.default let pubZoom = center.publisher(for: Notification.didToggleSplitZoom) @@ -77,7 +75,6 @@ extension Ghostty { .onReceive(pubZoom) { onZoom(notification: $0) } } } - .navigationTitle(surfaceTitle ?? "Ghostty") .id(node) // Needed for change detection on node } else { // On these events we want to reset the split state and call it. diff --git a/macos/Sources/Ghostty/InspectorView.swift b/macos/Sources/Ghostty/InspectorView.swift index b6147647e..a6e80bd47 100644 --- a/macos/Sources/Ghostty/InspectorView.swift +++ b/macos/Sources/Ghostty/InspectorView.swift @@ -31,7 +31,6 @@ extension Ghostty { }, right: { InspectorViewRepresentable(surfaceView: surfaceView) .focused($inspectorFocus) - .focusedValue(\.ghosttySurfaceTitle, surfaceView.title) .focusedValue(\.ghosttySurfaceView, surfaceView) }) } diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 3b9c10067..1e9a4cfef 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -6,14 +6,12 @@ extension Ghostty { /// Render a terminal for the active app in the environment. struct Terminal: View { @EnvironmentObject private var ghostty: Ghostty.App - @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? var body: some View { if let app = self.ghostty.app { SurfaceForApp(app) { surfaceView in SurfaceWrapper(surfaceView: surfaceView) } - .navigationTitle(surfaceTitle ?? "Ghostty") } } } @@ -83,7 +81,6 @@ extension Ghostty { Surface(view: surfaceView, size: geo.size) .focused($surfaceFocus) - .focusedValue(\.ghosttySurfaceTitle, title) .focusedValue(\.ghosttySurfacePwd, surfaceView.pwd) .focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) @@ -496,15 +493,6 @@ extension FocusedValues { typealias Value = Ghostty.SurfaceView } - var ghosttySurfaceTitle: String? { - get { self[FocusedGhosttySurfaceTitle.self] } - set { self[FocusedGhosttySurfaceTitle.self] = newValue } - } - - struct FocusedGhosttySurfaceTitle: FocusedValueKey { - typealias Value = String - } - var ghosttySurfacePwd: String? { get { self[FocusedGhosttySurfacePwd.self] } set { self[FocusedGhosttySurfacePwd.self] = newValue }