macos: hide dock globally if the dock conflicts (#5363)

Related to #5361

The fix in 5361 wasn't sufficient since it only applied if our app was
in the foreground. Our quick terminal is a non-activating NSPanel to
allow it to work on any space (fullscreen included). This means that
Ghostty doesn't become the active app when the quick terminal is shown
and another app is in the foreground.

To work around this, we now hide the dock globally when the quick
terminal is shown AND the dock is in a conflicting position. We restore
this state when the quick terminal is hidden, loses focus, or Ghostty is
quit.
This commit is contained in:
Mitchell Hashimoto
2025-01-24 20:30:47 -08:00
committed by GitHub
2 changed files with 79 additions and 20 deletions

View File

@ -27,9 +27,8 @@ class QuickTerminalController: BaseTerminalController {
// The active space when the quick terminal was last shown. // The active space when the quick terminal was last shown.
private var previousActiveSpace: size_t = 0 private var previousActiveSpace: size_t = 0
/// This is set to true of the dock was autohid when the terminal animated in. This lets us /// Non-nil if we have hidden dock state.
/// know if we have to unhide when the terminal is animated out. private var hiddenDock: HiddenDock? = nil
private var hidDock: Bool = false
/// The configuration derived from the Ghostty config so we don't need to rely on references. /// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig private var derivedConfig: DerivedConfig
@ -45,6 +44,11 @@ class QuickTerminalController: BaseTerminalController {
// Setup our notifications for behaviors // Setup our notifications for behaviors
let center = NotificationCenter.default let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(applicationWillTerminate(_:)),
name: NSApplication.willTerminateNotification,
object: nil)
center.addObserver( center.addObserver(
self, self,
selector: #selector(onToggleFullscreen), selector: #selector(onToggleFullscreen),
@ -65,6 +69,9 @@ class QuickTerminalController: BaseTerminalController {
// Remove all of our notificationcenter subscriptions // Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default let center = NotificationCenter.default
center.removeObserver(self) center.removeObserver(self)
// Make sure we restore our hidden dock
hiddenDock = nil
} }
// MARK: NSWindowController // MARK: NSWindowController
@ -100,6 +107,17 @@ class QuickTerminalController: BaseTerminalController {
// MARK: NSWindowDelegate // MARK: NSWindowDelegate
override func windowDidBecomeKey(_ notification: Notification) {
super.windowDidBecomeKey(notification)
// If we're not visible we don't care to run the logic below. It only
// applies if we can be seen.
guard visible else { return }
// Re-hide the dock if we were hiding it before.
hiddenDock?.hide()
}
override func windowDidResignKey(_ notification: Notification) { override func windowDidResignKey(_ notification: Notification) {
super.windowDidResignKey(notification) super.windowDidResignKey(notification)
@ -120,6 +138,10 @@ class QuickTerminalController: BaseTerminalController {
self.previousApp = nil self.previousApp = nil
} }
// Regardless of autohide, we always want to bring the dock back
// when we lose focus.
hiddenDock?.restore()
if derivedConfig.quickTerminalAutoHide { if derivedConfig.quickTerminalAutoHide {
switch derivedConfig.quickTerminalSpaceBehavior { switch derivedConfig.quickTerminalSpaceBehavior {
case .remain: case .remain:
@ -228,18 +250,6 @@ class QuickTerminalController: BaseTerminalController {
animateWindowOut(window: window, to: position) animateWindowOut(window: window, to: position)
} }
private func hideDock() {
guard !hidDock else { return }
NSApp.acquirePresentationOption(.autoHideDock)
hidDock = true
}
private func unhideDock() {
guard hidDock else { return }
NSApp.releasePresentationOption(.autoHideDock)
hidDock = false
}
private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) { private func animateWindowIn(window: NSWindow, from position: QuickTerminalPosition) {
guard let screen = derivedConfig.quickTerminalScreen.screen else { return } guard let screen = derivedConfig.quickTerminalScreen.screen else { return }
@ -259,7 +269,15 @@ class QuickTerminalController: BaseTerminalController {
// If our dock position would conflict with our target location then // If our dock position would conflict with our target location then
// we autohide the dock. // we autohide the dock.
if position.conflictsWithDock(on: screen) { if position.conflictsWithDock(on: screen) {
hideDock() if (hiddenDock == nil) {
hiddenDock = .init()
}
hiddenDock?.hide()
} else {
// Ensure we don't have any hidden dock if we don't conflict.
// The deinit will restore.
hiddenDock = nil
} }
// Run the animation that moves our window into the proper place and makes // Run the animation that moves our window into the proper place and makes
@ -274,7 +292,7 @@ class QuickTerminalController: BaseTerminalController {
DispatchQueue.main.async { DispatchQueue.main.async {
// If we canceled our animation clean up some state. // If we canceled our animation clean up some state.
guard self.visible else { guard self.visible else {
self.unhideDock() self.hiddenDock = nil
return return
} }
@ -346,7 +364,7 @@ class QuickTerminalController: BaseTerminalController {
private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) { private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
// If we hid the dock then we unhide it. // If we hid the dock then we unhide it.
unhideDock() hiddenDock = nil
// If the window isn't on our active space then we don't animate, we just // If the window isn't on our active space then we don't animate, we just
// hide it. // hide it.
@ -443,6 +461,13 @@ class QuickTerminalController: BaseTerminalController {
// MARK: Notifications // MARK: Notifications
@objc private func applicationWillTerminate(_ notification: Notification) {
// If the application is going to terminate we want to make sure we
// restore any global dock state. I think deinit should be called which
// would call this anyways but I can't be sure so I will do this too.
hiddenDock = nil
}
@objc private func onToggleFullscreen(notification: SwiftUI.Notification) { @objc private func onToggleFullscreen(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return } guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return } guard target == self.focusedSurface else { return }
@ -490,6 +515,35 @@ class QuickTerminalController: BaseTerminalController {
self.backgroundOpacity = config.backgroundOpacity self.backgroundOpacity = config.backgroundOpacity
} }
} }
/// Hides the dock globally (not just NSApp). This is only used if the quick terminal is
/// in a conflicting position with the dock.
private class HiddenDock {
let previousAutoHide: Bool
private var hidden: Bool = false
init() {
previousAutoHide = Dock.autoHideEnabled
}
deinit {
restore()
}
func hide() {
guard !hidden else { return }
NSApp.acquirePresentationOption(.autoHideDock)
Dock.autoHideEnabled = true
hidden = true
}
func restore() {
guard hidden else { return }
NSApp.releasePresentationOption(.autoHideDock)
Dock.autoHideEnabled = previousAutoHide
hidden = false
}
}
} }
extension Notification.Name { extension Notification.Name {

View File

@ -10,6 +10,10 @@ func CoreDockGetOrientationAndPinning(
@_silgen_name("CoreDockGetAutoHideEnabled") @_silgen_name("CoreDockGetAutoHideEnabled")
func CoreDockGetAutoHideEnabled() -> Bool func CoreDockGetAutoHideEnabled() -> Bool
// Toggles the Dock's auto-hide state
@_silgen_name("CoreDockSetAutoHideEnabled")
func CoreDockSetAutoHideEnabled(_ flag: Bool)
enum DockOrientation: Int { enum DockOrientation: Int {
case top = 1 case top = 1
case bottom = 2 case bottom = 2
@ -26,8 +30,9 @@ class Dock {
return .init(rawValue: Int(orientation)) ?? nil return .init(rawValue: Int(orientation)) ?? nil
} }
/// Returns true if the dock has auto-hide enabled. /// Set the dock autohide.
static var autoHideEnabled: Bool { static var autoHideEnabled: Bool {
return CoreDockGetAutoHideEnabled() get { return CoreDockGetAutoHideEnabled() }
set { CoreDockSetAutoHideEnabled(newValue) }
} }
} }