mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macOS: move window title handling fully to AppKit
Fixes #7236 Supersedes #7249 This removes all of our `focusedValue`-based tracking of the surface title and moves it completely to the window controller. The window controller now sets up event listeners (via Combine) when the focused surface changes and updates the window title accordingly. There is some complicated logic here to handle when we lose focus to something other than a surface. In this case, we want our title to be the last focused surface so long as it exists.
This commit is contained in:
@ -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<AnyCancellable> = []
|
||||
|
||||
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) {
|
||||
|
@ -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<ViewModel: TerminalViewModel>: 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<ViewModel: TerminalViewModel>: 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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -31,7 +31,6 @@ extension Ghostty {
|
||||
}, right: {
|
||||
InspectorViewRepresentable(surfaceView: surfaceView)
|
||||
.focused($inspectorFocus)
|
||||
.focusedValue(\.ghosttySurfaceTitle, surfaceView.title)
|
||||
.focusedValue(\.ghosttySurfaceView, surfaceView)
|
||||
})
|
||||
}
|
||||
|
@ -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 }
|
||||
|
Reference in New Issue
Block a user