Skip to content

Commit

Permalink
macos: prevent moveFocus from being an infinite loop (#2905)
Browse files Browse the repository at this point in the history
Fixes #2900

It's possible for moveFocus to infinite loop if the surface view we're
trying to move focus to NEVER gets attached to a window. This can happen
if the window is destroyed.

I think this issue should be more systemically fixed so it can't happen
but this workaround for now prevents moveFocus from being an infinite
loop source for the time being.
  • Loading branch information
mitchellh authored Dec 8, 2024
2 parents 0f5df65 + eb13893 commit ddf59e7
Showing 1 changed file with 32 additions and 3 deletions.
35 changes: 32 additions & 3 deletions macos/Sources/Ghostty/Ghostty.TerminalSplit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -429,12 +429,34 @@ extension Ghostty {
/// will lose focus. There has to be some nice SwiftUI-native way to fix this but I can't
/// figure it out so we're going to do this hacky thing to bring focus back to the terminal
/// that should have it.
static func moveFocus(to: SurfaceView, from: SurfaceView? = nil) {
DispatchQueue.main.async {
static func moveFocus(
to: SurfaceView,
from: SurfaceView? = nil,
delay: TimeInterval? = nil
) {
// The whole delay machinery is a bit of a hack to work around a
// situation where the window is destroyed and the surface view
// will never be attached to a window. Realistically, we should
// handle this upstream but we also don't want this function to be
// a source of infinite loops.

// Our max delay before we give up
let maxDelay: TimeInterval = 0.5
guard (delay ?? 0) < maxDelay else { return }

// We start at a 50 millisecond delay and do a doubling backoff
let nextDelay: TimeInterval = if let delay {
delay * 2
} else {
// 100 milliseconds
0.05
}

let work: DispatchWorkItem = .init {
// If the callback runs before the surface is attached to a view
// then the window will be nil. We just reschedule in that case.
guard let window = to.window else {
moveFocus(to: to, from: from)
moveFocus(to: to, from: from, delay: nextDelay)
return
}

Expand All @@ -448,5 +470,12 @@ extension Ghostty {

window.makeFirstResponder(to)
}

let queue = DispatchQueue.main
if let delay {
queue.asyncAfter(deadline: .now() + delay, execute: work)
} else {
queue.async(execute: work)
}
}
}

0 comments on commit ddf59e7

Please sign in to comment.