From e9556ba5f4528e2ccb2c56bb602cc9c27a1d498b Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Thu, 12 Mar 2026 02:20:10 -0700 Subject: [PATCH] Prevent background terminal focus retries from reordering windows --- GhosttyTabs.xcodeproj/project.pbxproj | 8 +++- Sources/GhosttyTerminalView.swift | 17 +++++++ ...sttyEnsureFocusWindowActivationTests.swift | 46 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 cmuxTests/GhosttyEnsureFocusWindowActivationTests.swift diff --git a/GhosttyTabs.xcodeproj/project.pbxproj b/GhosttyTabs.xcodeproj/project.pbxproj index a786a5c9..67318d7f 100644 --- a/GhosttyTabs.xcodeproj/project.pbxproj +++ b/GhosttyTabs.xcodeproj/project.pbxproj @@ -92,6 +92,7 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */; }; F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */; }; F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */; }; + F9000000A1B2C3D4E5F60718 /* GhosttyEnsureFocusWindowActivationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9000001A1B2C3D4E5F60718 /* GhosttyEnsureFocusWindowActivationTests.swift */; }; A5008381 /* BrowserFindJavaScriptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008380 /* BrowserFindJavaScriptTests.swift */; }; A5008383 /* CommandPaletteSearchEngineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5008382 /* CommandPaletteSearchEngineTests.swift */; }; DA7A10CA710E000000000003 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = DA7A10CA710E000000000001 /* Localizable.xcstrings */; }; @@ -235,8 +236,9 @@ F5000001A1B2C3D4E5F60718 /* SessionPersistenceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionPersistenceTests.swift; sourceTree = ""; }; F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegateShortcutRoutingTests.swift; sourceTree = ""; }; F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceContentViewVisibilityTests.swift; sourceTree = ""; }; - F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; - A5008380 /* BrowserFindJavaScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserFindJavaScriptTests.swift; sourceTree = ""; }; + F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocketControlPasswordStoreTests.swift; sourceTree = ""; }; + F9000001A1B2C3D4E5F60718 /* GhosttyEnsureFocusWindowActivationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GhosttyEnsureFocusWindowActivationTests.swift; sourceTree = ""; }; + A5008380 /* BrowserFindJavaScriptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserFindJavaScriptTests.swift; sourceTree = ""; }; A5008382 /* CommandPaletteSearchEngineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandPaletteSearchEngineTests.swift; sourceTree = ""; }; DA7A10CA710E000000000001 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; DA7A10CA710E000000000002 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; @@ -469,6 +471,7 @@ F6000001A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift */, F7000001A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift */, F8000001A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift */, + F9000001A1B2C3D4E5F60718 /* GhosttyEnsureFocusWindowActivationTests.swift */, A5008380 /* BrowserFindJavaScriptTests.swift */, A5008382 /* CommandPaletteSearchEngineTests.swift */, ); @@ -707,6 +710,7 @@ F6000000A1B2C3D4E5F60718 /* AppDelegateShortcutRoutingTests.swift in Sources */, F7000000A1B2C3D4E5F60718 /* WorkspaceContentViewVisibilityTests.swift in Sources */, F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */, + F9000000A1B2C3D4E5F60718 /* GhosttyEnsureFocusWindowActivationTests.swift in Sources */, A5008381 /* BrowserFindJavaScriptTests.swift in Sources */, A5008383 /* CommandPaletteSearchEngineTests.swift in Sources */, ); diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 771cfa38..65e50eaa 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -5592,6 +5592,15 @@ private final class GhosttyPassthroughVisualEffectView: NSVisualEffectView { } } +func shouldAllowEnsureFocusWindowActivation( + activeTabManager: TabManager?, + targetTabManager: TabManager, + keyWindow: NSWindow?, + mainWindow: NSWindow? +) -> Bool { + activeTabManager === targetTabManager || (keyWindow == nil && mainWindow == nil) +} + final class GhosttySurfaceScrollView: NSView { enum FlashStyle { case standardFocus @@ -7003,6 +7012,14 @@ final class GhosttySurfaceScrollView: NSView { } if !window.isKeyWindow { + guard shouldAllowEnsureFocusWindowActivation( + activeTabManager: delegate.tabManager, + targetTabManager: tabManager, + keyWindow: NSApp.keyWindow, + mainWindow: NSApp.mainWindow + ) else { + return + } window.makeKeyAndOrderFront(nil) } let result = window.makeFirstResponder(surfaceView) diff --git a/cmuxTests/GhosttyEnsureFocusWindowActivationTests.swift b/cmuxTests/GhosttyEnsureFocusWindowActivationTests.swift new file mode 100644 index 00000000..219aac46 --- /dev/null +++ b/cmuxTests/GhosttyEnsureFocusWindowActivationTests.swift @@ -0,0 +1,46 @@ +import XCTest +import AppKit + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +@MainActor +final class GhosttyEnsureFocusWindowActivationTests: XCTestCase { + func testAllowsActivationForActiveManager() { + let activeManager = TabManager() + let otherManager = TabManager() + + XCTAssertTrue( + shouldAllowEnsureFocusWindowActivation( + activeTabManager: activeManager, + targetTabManager: activeManager, + keyWindow: NSWindow(), + mainWindow: NSWindow() + ) + ) + XCTAssertFalse( + shouldAllowEnsureFocusWindowActivation( + activeTabManager: activeManager, + targetTabManager: otherManager, + keyWindow: NSWindow(), + mainWindow: NSWindow() + ) + ) + } + + func testAllowsActivationWhenAppHasNoKeyOrMainWindow() { + let targetManager = TabManager() + + XCTAssertTrue( + shouldAllowEnsureFocusWindowActivation( + activeTabManager: nil, + targetTabManager: targetManager, + keyWindow: nil, + mainWindow: nil + ) + ) + } +}