mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
Add delay before a title change to avoid flicker on macOS (#2929)
Implements #2503 for macOS This PR introduces a delay (75ms) before a title change to avoid the flicker caused by fast running commands. It adds a timer to `titleDidChange` in the [BaseTerminalController](https://github.com/ghostty-org/ghostty/blob/main/macos/Sources/Features/Terminal/BaseTerminalController.swift) to delay the window title update. However, this alone didn't work with `macos-titlebar-style = tabs` as the main window title is hidden and toolbar tab titles are set separately in [TerminalController](https://github.com/ghostty-org/ghostty/blob/main/macos/Sources/Features/Terminal/TerminalController.swift). I have added another timer in there to avoid flicker for tab titles. I also tried @qwerasd205's suggested approach by putting the timer in the [SurfaceView](https://github.com/ghostty-org/ghostty/blob/main/macos/Sources/Ghostty/SurfaceView_AppKit.swift) and setting it inside `setTitle` in [Ghostty.App](https://github.com/ghostty-org/ghostty/blob/main/macos/Sources/Ghostty/Ghostty.App.swift), and that also works. Maybe this a better approach as it avoids having two timers? There is still a flicker from the title changing from default "👻 Ghostty" to the current directory when opening new tabs but neither approach fixes that. Not too sure how to prevent that. Before: https://github.com/user-attachments/assets/bd87ff36-9888-4d01-904a-2e80b8edea18 After: https://github.com/user-attachments/assets/dae8eb24-8ae0-4412-ab84-bf313aac8063
This commit is contained in:
@ -947,14 +947,7 @@ extension Ghostty {
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
guard let title = String(cString: v.title!, encoding: .utf8) else { return }
|
||||
|
||||
// We must set this in a dispatchqueue to avoid a deadlock on startup on some
|
||||
// versions of macOS. I unfortunately didn't document the exact versions so
|
||||
// I don't know when its safe to remove this.
|
||||
DispatchQueue.main.async {
|
||||
surfaceView.title = title
|
||||
}
|
||||
|
||||
surfaceView.setTitle(title)
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
@ -1089,7 +1082,10 @@ extension Ghostty {
|
||||
guard let surface = target.target.surface else { return }
|
||||
guard let surfaceView = self.surfaceView(from: surface) else { return }
|
||||
let backingSize = NSSize(width: Double(v.width), height: Double(v.height))
|
||||
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
|
||||
DispatchQueue.main.async { [weak surfaceView] in
|
||||
guard let surfaceView else { return }
|
||||
surfaceView.cellSize = surfaceView.convertFromBacking(backingSize)
|
||||
}
|
||||
|
||||
default:
|
||||
assertionFailure()
|
||||
|
@ -12,7 +12,7 @@ extension Ghostty {
|
||||
// The current title of the surface as defined by the pty. This can be
|
||||
// changed with escape codes. This is public because the callbacks go
|
||||
// to the app level and it is set from there.
|
||||
@Published var title: String = "👻"
|
||||
@Published private(set) var title: String = "👻"
|
||||
|
||||
// The current pwd of the surface as defined by the pty. This can be
|
||||
// changed with escape codes.
|
||||
@ -110,6 +110,9 @@ extension Ghostty {
|
||||
// This is set to non-null during keyDown to accumulate insertText contents
|
||||
private var keyTextAccumulator: [String]? = nil
|
||||
|
||||
// A small delay that is introduced before a title change to avoid flickers
|
||||
private var titleChangeTimer: Timer?
|
||||
|
||||
// We need to support being a first responder so that we can get input events
|
||||
override var acceptsFirstResponder: Bool { return true }
|
||||
|
||||
@ -339,6 +342,20 @@ extension Ghostty {
|
||||
NSCursor.setHiddenUntilMouseMoves(!visible)
|
||||
}
|
||||
|
||||
func setTitle(_ title: String) {
|
||||
// This fixes an issue where very quick changes to the title could
|
||||
// cause an unpleasant flickering. We set a timer so that we can
|
||||
// coalesce rapid changes. The timer is short enough that it still
|
||||
// feels "instant".
|
||||
titleChangeTimer?.invalidate()
|
||||
titleChangeTimer = Timer.scheduledTimer(
|
||||
withTimeInterval: 0.075,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
self?.title = title
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc private func onUpdateRendererHealth(notification: SwiftUI.Notification) {
|
||||
|
Reference in New Issue
Block a user