Reduce ghost emoji flash in title bar (#4804)

Fixes #4799

This PR attempts to reduce the flash caused by the ghost emoji in the
title bar when opening new windows.

## Changes:

- Initialize `SurfaceView.title` with empty string instead of ghost
emoji

- Simplify title computation logic in `TerminalView`

- Adding a 500ms fallback timer for "👻"

	- Canceling timer if title is set

## Current Status:

While these changes reduce the initial ghost emoji flash, there's still
a brief moment where a folder emoji appears alone in the title bar when
opening a new window. This suggests there might be a race condition or
timing issue with how the title is being set and updated.


https://github.com/user-attachments/assets/3688c9f3-1727-4379-b04d-0bd6ac105728

Would appreciate feedback on the remaining flash issue and suggestions
for further improvements.
This commit is contained in:
Mitchell Hashimoto
2025-01-10 13:46:45 -08:00
committed by GitHub
2 changed files with 23 additions and 11 deletions

View File

@ -10,7 +10,7 @@ protocol TerminalViewDelegate: AnyObject {
/// The title of the terminal should change. /// The title of the terminal should change.
func titleDidChange(to: String) func titleDidChange(to: String)
/// The URL of the pwd should change. /// The URL of the pwd should change.
func pwdDidChange(to: URL?) func pwdDidChange(to: URL?)
@ -56,15 +56,10 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
// The title for our window // The title for our window
private var title: String { private var title: String {
var title = "👻" if let surfaceTitle, !surfaceTitle.isEmpty {
return surfaceTitle
if let surfaceTitle = surfaceTitle {
if (surfaceTitle.count > 0) {
title = surfaceTitle
}
} }
return "👻"
return title
} }
// The pwd of the focused surface as a URL // The pwd of the focused surface as a URL
@ -72,7 +67,7 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
guard let surfacePwd, surfacePwd != "" else { return nil } guard let surfacePwd, surfacePwd != "" else { return nil }
return URL(fileURLWithPath: surfacePwd) return URL(fileURLWithPath: surfacePwd)
} }
var body: some View { var body: some View {
switch ghostty.readiness { switch ghostty.readiness {
case .loading: case .loading:

View File

@ -12,7 +12,14 @@ extension Ghostty {
// The current title of the surface as defined by the pty. This can be // 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 // changed with escape codes. This is public because the callbacks go
// to the app level and it is set from there. // to the app level and it is set from there.
@Published private(set) var title: String = "👻" @Published private(set) var title: String = "" {
didSet {
if !title.isEmpty {
titleFallbackTimer?.invalidate()
titleFallbackTimer = nil
}
}
}
// The current pwd of the surface as defined by the pty. This can be // The current pwd of the surface as defined by the pty. This can be
// changed with escape codes. // changed with escape codes.
@ -113,6 +120,9 @@ extension Ghostty {
// A small delay that is introduced before a title change to avoid flickers // A small delay that is introduced before a title change to avoid flickers
private var titleChangeTimer: Timer? private var titleChangeTimer: Timer?
// A timer to fallback to ghost emoji if no title is set within the grace period
private var titleFallbackTimer: Timer?
/// Event monitor (see individual events for why) /// Event monitor (see individual events for why)
private var eventMonitor: Any? = nil private var eventMonitor: Any? = nil
@ -139,6 +149,13 @@ extension Ghostty {
// can do SOMETHING. // can do SOMETHING.
super.init(frame: NSMakeRect(0, 0, 800, 600)) super.init(frame: NSMakeRect(0, 0, 800, 600))
// Set a timer to show the ghost emoji after 500ms if no title is set
titleFallbackTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { [weak self] _ in
if let self = self, self.title.isEmpty {
self.title = "👻"
}
}
// Before we initialize the surface we want to register our notifications // Before we initialize the surface we want to register our notifications
// so there is no window where we can't receive them. // so there is no window where we can't receive them.
let center = NotificationCenter.default let center = NotificationCenter.default