diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 9494b4339..502eb3e6a 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */; }; A52FFF592CAA4FF3000C6A5B /* Fullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */; }; A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */; }; + A52FFF5D2CAB4D08000C6A5B /* NSScreen+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */; }; A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; }; A5333E1D2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */; }; A5333E202B5A2111008AEFF7 /* SurfaceView_UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */; }; @@ -107,6 +108,7 @@ A52FFF562CA90481000C6A5B /* QuickTerminalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalScreen.swift; sourceTree = ""; }; A52FFF582CAA4FF1000C6A5B /* Fullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fullscreen.swift; sourceTree = ""; }; A52FFF5A2CAA54A8000C6A5B /* FullscreenMode+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FullscreenMode+Extension.swift"; sourceTree = ""; }; + A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSScreen+Extension.swift"; sourceTree = ""; }; A5333E152B59DE8E008AEFF7 /* SurfaceView_UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_UIKit.swift; sourceTree = ""; }; A5333E1B2B5A1CE3008AEFF7 /* CrossKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossKit.swift; sourceTree = ""; }; A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = ""; }; @@ -240,6 +242,7 @@ A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, A5CBD0552C9E65A50017A1AE /* DraggableWindowView.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, + A52FFF5C2CAB4D05000C6A5B /* NSScreen+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, A5985CD62C320C4500C57AD3 /* String+Extension.swift */, @@ -576,6 +579,7 @@ A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */, A5CBD06B2CA322430017A1AE /* GlobalEventTap.swift in Sources */, AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */, + A52FFF5D2CAB4D08000C6A5B /* NSScreen+Extension.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, A5CBD0582C9F30960017A1AE /* Cursor.swift in Sources */, A52FFF5B2CAA54B1000C6A5B /* FullscreenMode+Extension.swift in Sources */, diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index 937e77dce..460a854ee 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -129,33 +129,24 @@ class NonNativeFullscreen: FullscreenStyle { guard let savedState = SavedState(window) else { return } self.savedState = savedState - // Change presentation style to hide menu bar and dock if needed - // It's important to do this in two calls, because setting them in a single call guarantees - // that the menu bar will also be hidden on any additional displays (why? nobody knows!) - // When these options are set separately, the menu bar hiding problem will only occur in - // specific scenarios. More investigation is needed to pin these scenarios down precisely, - // but it seems to have something to do with which app had focus last. - // Furthermore, it's much easier to figure out which screen the dock is on if the menubar - // has not yet been hidden, so the order matters here! + // We hide the dock if the window is on a screen with the dock. + if (savedState.dock) { + hideDock() - // We always hide the dock. There are many scenarios where we don't - // need to (dock is not on this screen, dock is already hidden, etc.) - // but I don't think there's a downside to just unconditionally doing this. - hideDock() + // Hide the dock whenever this window becomes focused. + NotificationCenter.default.addObserver( + self, + selector: #selector(hideDock), + name: NSWindow.didBecomeMainNotification, + object: window) - // Hide the dock whenever this window becomes focused. - NotificationCenter.default.addObserver( - self, - selector: #selector(hideDock), - name: NSWindow.didBecomeMainNotification, - object: window) - - // Unhide the dock whenever this window becomes unfocused. - NotificationCenter.default.addObserver( - self, - selector: #selector(unhideDock), - name: NSWindow.didResignMainNotification, - object: window) + // Unhide the dock whenever this window becomes unfocused. + NotificationCenter.default.addObserver( + self, + selector: #selector(unhideDock), + name: NSWindow.didResignMainNotification, + object: window) + } // Hide the menu if requested if (properties.hideMenu) { @@ -209,7 +200,9 @@ class NonNativeFullscreen: FullscreenStyle { NotificationCenter.default.removeObserver(self) // Unhide our elements - unhideDock() + if savedState.dock { + unhideDock() + } unhideMenu() // Restore our saved state @@ -317,6 +310,7 @@ class NonNativeFullscreen: FullscreenStyle { let tabGroupIndex: Int? let contentFrame: NSRect let styleMask: NSWindow.StyleMask + let dock: Bool init?(_ window: NSWindow) { guard let contentView = window.contentView else { return nil } @@ -325,6 +319,7 @@ class NonNativeFullscreen: FullscreenStyle { self.tabGroupIndex = window.tabGroup?.windows.firstIndex(of: window) self.contentFrame = window.convertToScreen(contentView.frame) self.styleMask = window.styleMask + self.dock = window.screen?.hasDock ?? false } } } diff --git a/macos/Sources/Helpers/NSScreen+Extension.swift b/macos/Sources/Helpers/NSScreen+Extension.swift new file mode 100644 index 000000000..f5a08b524 --- /dev/null +++ b/macos/Sources/Helpers/NSScreen+Extension.swift @@ -0,0 +1,36 @@ +import Cocoa + +extension NSScreen { + // Returns true if the given screen has a visible dock. This isn't + // point-in-time visible, this is true if the dock is always visible + // AND present on this screen. + var hasDock: Bool { + // If the dock autohides then we don't have a dock ever. + if let dockAutohide = UserDefaults.standard.persistentDomain(forName: "com.apple.dock")?["autohide"] as? Bool { + if (dockAutohide) { return false } + } + + // There is no public API to directly ask about dock visibility, so we have to figure it out + // by comparing the sizes of visibleFrame (the currently usable area of the screen) and + // frame (the full screen size). We also need to account for the menubar, any inset caused + // by the notch on macbooks, and a little extra padding to compensate for the boundary area + // which triggers showing the dock. + + // If our visible width is less than the frame we assume its the dock. + if (visibleFrame.width < frame.width) { + return true + } + + // We need to see if our visible frame height is less than the full + // screen height minus the menu and notch and such. + let menuHeight = NSApp.mainMenu?.menuBarHeight ?? 0 + let notchInset: CGFloat = if #available(macOS 12, *) { + safeAreaInsets.top + } else { + 0 + } + let boundaryAreaPadding = 5.0 + + return visibleFrame.height < (frame.height - max(menuHeight, notchInset) - boundaryAreaPadding) + } +}