Skip to content

Commit

Permalink
macos: hide dock globally if the dock conflicts
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mitchellh committed Jan 25, 2025
1 parent e2e6770 commit a58b199
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 20 deletions.
90 changes: 72 additions & 18 deletions macos/Sources/Features/QuickTerminal/QuickTerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ class QuickTerminalController: BaseTerminalController {
// The active space when the quick terminal was last shown.
private var previousActiveSpace: size_t = 0

/// This is set to true of the dock was autohid when the terminal animated in. This lets us
/// know if we have to unhide when the terminal is animated out.
private var hidDock: Bool = false
/// Non-nil if we have hidden dock state.
private var hiddenDock: HiddenDock? = nil

/// The configuration derived from the Ghostty config so we don't need to rely on references.
private var derivedConfig: DerivedConfig
Expand All @@ -45,6 +44,11 @@ class QuickTerminalController: BaseTerminalController {

// Setup our notifications for behaviors
let center = NotificationCenter.default
center.addObserver(
self,
selector: #selector(applicationWillTerminate(_:)),
name: NSApplication.willTerminateNotification,
object: nil)
center.addObserver(
self,
selector: #selector(onToggleFullscreen),
Expand All @@ -65,6 +69,9 @@ class QuickTerminalController: BaseTerminalController {
// Remove all of our notificationcenter subscriptions
let center = NotificationCenter.default
center.removeObserver(self)

// Make sure we restore our hidden dock
hiddenDock = nil
}

// MARK: NSWindowController
Expand Down Expand Up @@ -100,6 +107,17 @@ class QuickTerminalController: BaseTerminalController {

// 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) {
super.windowDidResignKey(notification)

Expand All @@ -120,6 +138,10 @@ class QuickTerminalController: BaseTerminalController {
self.previousApp = nil
}

// Regardless of autohide, we always want to bring the dock back
// when we lose focus.
hiddenDock?.restore()

if derivedConfig.quickTerminalAutoHide {
switch derivedConfig.quickTerminalSpaceBehavior {
case .remain:
Expand Down Expand Up @@ -228,18 +250,6 @@ class QuickTerminalController: BaseTerminalController {
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) {
guard let screen = derivedConfig.quickTerminalScreen.screen else { return }

Expand All @@ -259,7 +269,15 @@ class QuickTerminalController: BaseTerminalController {
// If our dock position would conflict with our target location then
// we autohide the dock.
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
Expand All @@ -274,7 +292,7 @@ class QuickTerminalController: BaseTerminalController {
DispatchQueue.main.async {
// If we canceled our animation clean up some state.
guard self.visible else {
self.unhideDock()
self.hiddenDock = nil
return
}

Expand Down Expand Up @@ -346,7 +364,7 @@ class QuickTerminalController: BaseTerminalController {

private func animateWindowOut(window: NSWindow, to position: QuickTerminalPosition) {
// 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
// hide it.
Expand Down Expand Up @@ -443,6 +461,13 @@ class QuickTerminalController: BaseTerminalController {

// 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) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
Expand Down Expand Up @@ -490,6 +515,35 @@ class QuickTerminalController: BaseTerminalController {
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 {
Expand Down
9 changes: 7 additions & 2 deletions macos/Sources/Helpers/Dock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func CoreDockGetOrientationAndPinning(
@_silgen_name("CoreDockGetAutoHideEnabled")
func CoreDockGetAutoHideEnabled() -> Bool

// Toggles the Dock's auto-hide state
@_silgen_name("CoreDockSetAutoHideEnabled")
func CoreDockSetAutoHideEnabled(_ flag: Bool)

enum DockOrientation: Int {
case top = 1
case bottom = 2
Expand All @@ -26,8 +30,9 @@ class Dock {
return .init(rawValue: Int(orientation)) ?? nil
}

/// Returns true if the dock has auto-hide enabled.
/// Set the dock autohide.
static var autoHideEnabled: Bool {
return CoreDockGetAutoHideEnabled()
get { return CoreDockGetAutoHideEnabled() }
set { CoreDockSetAutoHideEnabled(newValue) }
}
}

0 comments on commit a58b199

Please sign in to comment.