-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix blank terminal renders after workspace switches #1964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4015,7 +4015,9 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { | |||||||||||||||||||||||
| return true | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Visibility is used for focus gating, not for libghostty occlusion. | ||||||||||||||||||||||||
| // Visibility is used for focus gating. Explicit portal visibility transitions | ||||||||||||||||||||||||
| // also drive Ghostty occlusion so hidden workspace/split surfaces pause and | ||||||||||||||||||||||||
| // queue a redraw when they become visible again. | ||||||||||||||||||||||||
| fileprivate var isVisibleInUI: Bool { visibleInUI } | ||||||||||||||||||||||||
| fileprivate func setVisibleInUI(_ visible: Bool) { | ||||||||||||||||||||||||
| visibleInUI = visible | ||||||||||||||||||||||||
|
|
@@ -6590,6 +6592,7 @@ final class GhosttySurfaceScrollView: NSView { | |||||||||||||||||||||||
| private static let scrollToBottomThreshold: CGFloat = 5.0 | ||||||||||||||||||||||||
| private var isActive = true | ||||||||||||||||||||||||
| private var lastFocusRefreshAt: CFTimeInterval = 0 | ||||||||||||||||||||||||
| private var lastRequestedPortalOcclusionVisible: Bool? | ||||||||||||||||||||||||
| private var activeDropZone: DropZone? | ||||||||||||||||||||||||
| private var pendingDropZone: DropZone? | ||||||||||||||||||||||||
| private var dropZoneOverlayAnimationGeneration: UInt64 = 0 | ||||||||||||||||||||||||
|
|
@@ -7909,6 +7912,10 @@ final class GhosttySurfaceScrollView: NSView { | |||||||||||||||||||||||
| let wasVisible = surfaceView.isVisibleInUI | ||||||||||||||||||||||||
| surfaceView.setVisibleInUI(visible) | ||||||||||||||||||||||||
| isHidden = !visible | ||||||||||||||||||||||||
| if wasVisible != visible, lastRequestedPortalOcclusionVisible != visible { | ||||||||||||||||||||||||
| lastRequestedPortalOcclusionVisible = visible | ||||||||||||||||||||||||
| surfaceView.terminalSurface?.setOcclusion(visible) | ||||||||||||||||||||||||
|
Comment on lines
+7915
to
+7917
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Occlusion state is cached before confirming a live terminal surface, which can drop the occlusion transition and leave Ghostty visibility state out of sync. Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+7915
to
+7918
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In practice this is unlikely to be hit on the normal workspace-switch path (surfaces are attached before workspaces are shown/hidden), but the pattern can be made robust by only recording the intent after a successful call:
Suggested change
Alternatively, unconditionally updating the flag and adding a comment noting the nil-surface race is also acceptable if the surface lifecycle guarantees absence of the edge case. |
||||||||||||||||||||||||
| #if DEBUG | ||||||||||||||||||||||||
| if wasVisible != visible { | ||||||||||||||||||||||||
| let transition = "\(wasVisible ? 1 : 0)->\(visible ? 1 : 0)" | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replay occlusion when the runtime surface changes.
lastRequestedPortalOcclusionVisiblelives on the hosted view, but the actualghostty_surface_thas its own lifecycle. IfsetVisibleInUI(_:)runs beforecreateSurface(for:)finishes, or after the runtime surface is torn down/recreated, this cache can flip even though no occlusion was applied to the new surface. The next restore then sends onlytrue, so the replacement surface never sees the hidden→visible transition this fix is relying on. Reset/replay the cached occlusion when a runtime surface is created/rebound, or key the dedupe off the current runtime surface instead of the view.Based on learnings: "after calling view.forceRefreshSurface(), re-read self.surface because the surface may be torn down or reparented."
Also applies to: 7915-7918
🤖 Prompt for AI Agents