mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-17 09:16:11 +03:00
macos: rework toggle_visibility to better match iTerm2
Two major changes: 1. Hiding uses `NSApp.hide` which hides all windows, preserves tabs, and yields focus to the next app. 2. Unhiding manually tracks and brings forward only the windows we hid. Proper focus should be retained.
This commit is contained in:
@ -72,6 +72,7 @@
|
||||
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A6F7292CC41B8700B232A5 /* Xcode.swift */; };
|
||||
A5B30539299BEAAB0047F10C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
|
||||
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 */; };
|
||||
A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CBD0572C9F30860017A1AE /* Cursor.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -282,6 +284,7 @@
|
||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */,
|
||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
||||
A5CA378D2D31D6C100931030 /* Weak.swift */,
|
||||
C1F26EE72B76CBFC00404083 /* VibrantLayer.h */,
|
||||
C1F26EE82B76CBFC00404083 /* VibrantLayer.m */,
|
||||
A5CEAFDA29B8005900646FDA /* SplitView */,
|
||||
@ -647,6 +650,7 @@
|
||||
A5A6F72A2CC41B8900B232A5 /* Xcode.swift in Sources */,
|
||||
A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */,
|
||||
A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */,
|
||||
A5CA378E2D31D6C300931030 /* Weak.swift in Sources */,
|
||||
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,
|
||||
A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */,
|
||||
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
||||
|
@ -92,10 +92,8 @@ class AppDelegate: NSObject,
|
||||
return ProcessInfo.processInfo.systemUptime - applicationLaunchTime
|
||||
}
|
||||
|
||||
/// Tracks whether the application is currently visible. This can be gamed, i.e. if a user manually
|
||||
/// brings each window one by one to the front. But at worst its off by one set of toggles and this
|
||||
/// makes our logic very easy.
|
||||
private var isVisible: Bool = true
|
||||
/// Tracks the windows that we hid for toggleVisibility.
|
||||
private var hiddenWindows: [Weak<NSWindow>] = []
|
||||
|
||||
/// The observer for the app appearance.
|
||||
private var appearanceObserver: NSKeyValueObservation? = nil
|
||||
@ -219,15 +217,20 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
guard !applicationHasBecomeActive else { return }
|
||||
applicationHasBecomeActive = true
|
||||
// If we're back then clear the hidden windows
|
||||
self.hiddenWindows = []
|
||||
|
||||
// Let's launch our first window. We only do this if we have no other windows. It
|
||||
// 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()
|
||||
// First launch stuff
|
||||
if (!applicationHasBecomeActive) {
|
||||
applicationHasBecomeActive = true
|
||||
|
||||
// Let's launch our first window. We only do this if we have no other windows. It
|
||||
// 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,22 +709,23 @@ class AppDelegate: NSObject,
|
||||
|
||||
/// Toggles visibility of all Ghosty Terminal windows. When hidden, activates Ghostty as the frontmost application
|
||||
@IBAction func toggleVisibility(_ sender: Any) {
|
||||
if isVisible {
|
||||
NSApp.windows.forEach { window in
|
||||
window.alphaValue = 0.0
|
||||
}
|
||||
} else {
|
||||
NSApp.windows.forEach { window in
|
||||
window.alphaValue = 1.0
|
||||
}
|
||||
// If we have focus, then we hide all windows.
|
||||
if NSApp.isActive {
|
||||
// We need to keep track of the windows that were visible because we only
|
||||
// want to bring back these windows if we remove the toggle.
|
||||
self.hiddenWindows = NSApp.windows.filter { $0.isVisible }.map { Weak($0) }
|
||||
NSApp.hide(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// After bringing them all to front we make sure our app is active too.
|
||||
if !isVisible {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
isVisible.toggle()
|
||||
|
||||
// If we're not active, we want to become active
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
|
||||
// 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 {
|
||||
|
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,
|
||||
|
||||
/// 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
|
||||
/// not yield focus to the previous application.
|
||||
/// This currently only works on macOS.
|
||||
toggle_visibility: void,
|
||||
|
||||
/// Quit ghostty.
|
||||
|
Reference in New Issue
Block a user