mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
fix: ensure terminal windows don't part from parent window when toggling visibility (#4789)
As of version 1.0.1 (macOS build) when running the toggle visibility action a window with tabs is made into multiple windows. This PR ensures terminal tabs are reconstructed and correctly placed into its parent window. # Demo https://github.com/user-attachments/assets/44f14bca-15a1-4717-ba0a-44f0767feec3 FYI: I will create another PR to ensure the right tab is focused after the main window is restored. Solves #4329
This commit is contained in:
@ -72,6 +72,7 @@
|
|||||||
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
|
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
|
||||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||||
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
A5CA378C2D2A4DEB00931030 /* KeyboardLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */; };
|
||||||
|
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CA378D2D31D6C100931030 /* Weak.swift */; };
|
||||||
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
A5CBD0562C9E65B80017A1AE /* DraggableWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */; };
|
||||||
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; };
|
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.swift */; };
|
||||||
A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
A5CBD0592C9F37B10017A1AE /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; };
|
||||||
@ -167,6 +168,7 @@
|
|||||||
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
|
||||||
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
A5CA378B2D2A4DE800931030 /* KeyboardLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardLayout.swift; sourceTree = "<group>"; };
|
||||||
|
A5CA378D2D31D6C100931030 /* Weak.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Weak.swift; sourceTree = "<group>"; };
|
||||||
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggableWindowView.swift; sourceTree = "<group>"; };
|
||||||
A5CBD0572C9F30860017A1AE /* Cursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cursor.swift; sourceTree = "<group>"; };
|
A5CBD0572C9F30860017A1AE /* Cursor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cursor.swift; sourceTree = "<group>"; };
|
||||||
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuickTerminal.xib; sourceTree = "<group>"; };
|
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = QuickTerminal.xib; sourceTree = "<group>"; };
|
||||||
@ -282,6 +284,7 @@
|
|||||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
||||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||||
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
||||||
|
A5CA378D2D31D6C100931030 /* Weak.swift */,
|
||||||
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
||||||
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
||||||
A5CEAFDA29B8005900646FDA /* SplitView */,
|
A5CEAFDA29B8005900646FDA /* SplitView */,
|
||||||
@ -647,6 +650,7 @@
|
|||||||
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */,
|
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */,
|
||||||
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
|
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
|
||||||
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
|
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
|
||||||
|
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */,
|
||||||
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
||||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||||
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
||||||
|
@ -92,10 +92,8 @@ class AppDelegate: NSObject,
|
|||||||
return ProcessInfo.processInfo.systemUptime - applicationLaunchTime
|
return ProcessInfo.processInfo.systemUptime - applicationLaunchTime
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tracks whether the application is currently visible. This can be gamed, i.e. if a user manually
|
/// Tracks the windows that we hid for toggleVisibility.
|
||||||
/// brings each window one by one to the front. But at worst its off by one set of toggles and this
|
private var hiddenWindows: [Weak<NSWindow>] = []
|
||||||
/// makes our logic very easy.
|
|
||||||
private var isVisible: Bool = true
|
|
||||||
|
|
||||||
/// The observer for the app appearance.
|
/// The observer for the app appearance.
|
||||||
private var appearanceObserver: NSKeyValueObservation? = nil
|
private var appearanceObserver: NSKeyValueObservation? = nil
|
||||||
@ -219,15 +217,20 @@ class AppDelegate: NSObject,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applicationDidBecomeActive(_ notification: Notification) {
|
func applicationDidBecomeActive(_ notification: Notification) {
|
||||||
guard !applicationHasBecomeActive else { return }
|
// If we're back then clear the hidden windows
|
||||||
applicationHasBecomeActive = true
|
self.hiddenWindows = []
|
||||||
|
|
||||||
// Let's launch our first window. We only do this if we have no other windows. It
|
// First launch stuff
|
||||||
// is possible to have other windows in a few scenarios:
|
if (!applicationHasBecomeActive) {
|
||||||
// - if we're opening a URL since `application(_:openFile:)` is called before this.
|
applicationHasBecomeActive = true
|
||||||
// - if we're restoring from persisted state
|
|
||||||
if terminalManager.windows.count == 0 && derivedConfig.initialWindow {
|
// Let's launch our first window. We only do this if we have no other windows. It
|
||||||
terminalManager.newWindow()
|
// is possible to have other windows in a few scenarios:
|
||||||
|
// - if we're opening a URL since `application(_:openFile:)` is called before this.
|
||||||
|
// - if we're restoring from persisted state
|
||||||
|
if terminalManager.windows.count == 0 && derivedConfig.initialWindow {
|
||||||
|
terminalManager.newWindow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,21 +709,23 @@ class AppDelegate: NSObject,
|
|||||||
|
|
||||||
/// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
|
/// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
|
||||||
@IBAction func toggleVisibility(_ sender: Any) {
|
@IBAction func toggleVisibility(_ sender: Any) {
|
||||||
// We only care about terminal windows.
|
// If we have focus, then we hide all windows.
|
||||||
for window in NSApp.windows.filter({ $0.windowController is BaseTerminalController }) {
|
if NSApp.isActive {
|
||||||
if isVisible {
|
// We need to keep track of the windows that were visible because we only
|
||||||
window.orderOut(nil)
|
// want to bring back these windows if we remove the toggle.
|
||||||
} else {
|
self.hiddenWindows = NSApp.windows.filter { $0.isVisible }.map { Weak($0) }
|
||||||
window.makeKeyAndOrderFront(nil)
|
NSApp.hide(nil)
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// After bringing them all to front we make sure our app is active too.
|
// If we're not active, we want to become active
|
||||||
if !isVisible {
|
NSApp.activate(ignoringOtherApps: true)
|
||||||
NSApp.activate(ignoringOtherApps: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
isVisible.toggle()
|
// Bring all windows to the front. Note: we don't use NSApp.unhide because
|
||||||
|
// that will unhide ALL hidden windows. We want to only bring forward the
|
||||||
|
// ones that we hid.
|
||||||
|
self.hiddenWindows.forEach { $0.value?.orderFrontRegardless() }
|
||||||
|
self.hiddenWindows = []
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DerivedConfig {
|
private struct DerivedConfig {
|
||||||
|
9
macos/Sources/Helpers/Weak.swift
Normal file
9
macos/Sources/Helpers/Weak.swift
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/// A wrapper that holds a weak reference to an object. This lets us create native containers
|
||||||
|
/// of weak references.
|
||||||
|
class Weak<T: AnyObject> {
|
||||||
|
weak var value: T?
|
||||||
|
|
||||||
|
init(_ value: T) {
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
@ -441,10 +441,10 @@ pub const Action = union(enum) {
|
|||||||
toggle_quick_terminal: void,
|
toggle_quick_terminal: void,
|
||||||
|
|
||||||
/// Show/hide all windows. If all windows become shown, we also ensure
|
/// Show/hide all windows. If all windows become shown, we also ensure
|
||||||
/// Ghostty is focused.
|
/// Ghostty becomes focused. When hiding all windows, focus is yielded
|
||||||
|
/// to the next application as determined by the OS.
|
||||||
///
|
///
|
||||||
/// This currently only works on macOS. When hiding all windows, we do
|
/// This currently only works on macOS.
|
||||||
/// not yield focus to the previous application.
|
|
||||||
toggle_visibility: void,
|
toggle_visibility: void,
|
||||||
|
|
||||||
/// Quit ghostty.
|
/// Quit ghostty.
|
||||||
|
Reference in New Issue
Block a user