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 */; };
|
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,22 +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) {
|
||||||
if isVisible {
|
// If we have focus, then we hide all windows.
|
||||||
NSApp.windows.forEach { window in
|
if NSApp.isActive {
|
||||||
window.alphaValue = 0.0
|
// 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.
|
||||||
} else {
|
self.hiddenWindows = NSApp.windows.filter { $0.isVisible }.map { Weak($0) }
|
||||||
NSApp.windows.forEach { window in
|
NSApp.hide(nil)
|
||||||
window.alphaValue = 1.0
|
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)
|
|
||||||
}
|
// 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
|
||||||
isVisible.toggle()
|
// 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