Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 0 additions & 61 deletions Sources/GhosttyTerminalView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,6 @@ class GhosttyApp {
}()
private let backgroundLogURL = URL(fileURLWithPath: "/tmp/cmux-bg.log")
private var appObservers: [NSObjectProtocol] = []
private var displayLink: CVDisplayLink?
private var displayLinkUsers = 0
private let displayLinkLock = NSLock()

// Scroll lag tracking
private(set) var isScrolling = false
Expand Down Expand Up @@ -294,7 +291,6 @@ class GhosttyApp {

let start = CACurrentMediaTime()
ghostty_app_tick(app)
AppDelegate.shared?.tabManager?.tickRender()
let elapsedMs = (CACurrentMediaTime() - start) * 1000
Comment on lines 291 to 294

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ensure Ghostty surfaces are still drawn each tick

With tickRender() removed, tick() now only calls ghostty_app_tick(app). I couldn’t find any remaining call to ghostty_surface_draw in the repo, which was previously the only render entry point. If GhosttyKit still expects the host to invoke ghostty_surface_draw to present frames, visible surfaces won’t repaint on output/cursor blink (only on size refresh via ghostty_surface_refresh). Please confirm GhosttyKit now draws internally on ghostty_app_tick, or reintroduce an explicit draw for visible surfaces to avoid frozen renders.

Useful? React with 👍 / 👎.


// Track lag during scrolling
Expand All @@ -305,49 +301,6 @@ class GhosttyApp {
}
}

func retainDisplayLink() {
displayLinkLock.lock()
defer { displayLinkLock.unlock() }
displayLinkUsers += 1
if displayLinkUsers == 1 {
startDisplayLink()
}
}

func releaseDisplayLink() {
displayLinkLock.lock()
defer { displayLinkLock.unlock() }
displayLinkUsers = max(0, displayLinkUsers - 1)
if displayLinkUsers == 0 {
stopDisplayLink()
}
}

private func startDisplayLink() {
if displayLink == nil {
var link: CVDisplayLink?
CVDisplayLinkCreateWithActiveCGDisplays(&link)
guard let newLink = link else { return }
displayLink = newLink
let callback: CVDisplayLinkOutputCallback = { _, _, _, _, _, _ -> CVReturn in
DispatchQueue.main.async {
GhosttyApp.shared.tick()
}
return kCVReturnSuccess
}
CVDisplayLinkSetOutputCallback(newLink, callback, nil)
}
if let displayLink, !CVDisplayLinkIsRunning(displayLink) {
CVDisplayLinkStart(displayLink)
}
}

private func stopDisplayLink() {
if let displayLink, CVDisplayLinkIsRunning(displayLink) {
CVDisplayLinkStop(displayLink)
}
}

private func updateDefaultBackground(from config: ghostty_config_t?) {
guard let config else { return }

Expand Down Expand Up @@ -715,7 +668,6 @@ final class TerminalSurface: Identifiable, ObservableObject {
private let workingDirectory: String?
let hostedView: GhosttySurfaceScrollView
private let surfaceView: GhosttyNSView
private var ownsDisplayLink = false
@Published var searchState: SearchState? = nil {
didSet {
if let searchState {
Expand Down Expand Up @@ -885,10 +837,6 @@ final class TerminalSurface: Identifiable, ObservableObject {
UInt32(view.bounds.height * scaleFactors.y)
)
ghostty_surface_refresh(surface)
if !ownsDisplayLink {
GhosttyApp.shared.retainDisplayLink()
ownsDisplayLink = true
}
}

private func updateMetalLayer(for view: GhosttyNSView) {
Expand Down Expand Up @@ -916,12 +864,6 @@ final class TerminalSurface: Identifiable, ObservableObject {
}
}

func renderIfVisible() {
guard let view = attachedView else { return }
guard view.window != nil, view.bounds.width > 0, view.bounds.height > 0 else { return }
ghostty_surface_draw(surface)
}

func applyWindowBackgroundIfActive() {
surfaceView.applyWindowBackgroundIfActive()
}
Expand Down Expand Up @@ -958,9 +900,6 @@ final class TerminalSurface: Identifiable, ObservableObject {
}

deinit {
if ownsDisplayLink {
GhosttyApp.shared.releaseDisplayLink()
}
if let surface = surface {
ghostty_surface_free(surface)
}
Expand Down
8 changes: 0 additions & 8 deletions Sources/TabManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,6 @@ class TabManager: ObservableObject {
selectedSurface?.searchState = nil
}

func tickRender() {
guard let selectedTabId,
let tab = tabs.first(where: { $0.id == selectedTabId }) else { return }
for surface in tab.splitTree.map({ $0 }) {
surface.renderIfVisible()
}
}

@discardableResult
func addTab() -> Tab {
let workingDirectory = preferredWorkingDirectoryForNewTab()
Expand Down
18 changes: 15 additions & 3 deletions tests/test_ctrl_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,24 @@ def test_environment_paths(client: cmux) -> TestResult:
"\"XDG_DATA_DIRS\": os.environ.get(\"XDG_DATA_DIRS\", \"\"),"
"}))'"
)
client.send(command + "\n")

for _ in range(20):
for attempt in range(3):
env_path.unlink(missing_ok=True)
# Reset any partial prompt state (e.g., unmatched quotes) before retrying.
client.send_ctrl_c()
time.sleep(0.2)
client.send(command + "\n")

for _ in range(20):
if env_path.exists():
break
time.sleep(0.2)

if env_path.exists():
break
time.sleep(0.2)

# Small backoff before retrying send in case the surface isn't ready yet.
time.sleep(0.3 * (attempt + 1))

if not env_path.exists():
result.failure("Env dump file was not created")
Expand Down