diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 68c243004..a2c026a56 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -333,7 +333,15 @@ class BaseTerminalController: NSWindowController, } } - func fullscreenDidChange() { + func fullscreenDidChange(mode: FullscreenMode, enabled: Bool) { + NotificationCenter.default.post( + name: Ghostty.Notification.ghosttyDidToggleFullscreen, + object: self.window, + userInfo: [ + Ghostty.Notification.FullscreenEnabledKey: enabled, + Ghostty.Notification.FullscreenModeKey: mode, + ] + ) // For some reason focus can get lost when we change fullscreen. Regardless of // mode above we just move it back. if let focusedSurface { diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 7fd1802dc..41b72ae3d 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -95,8 +95,8 @@ class TerminalController: BaseTerminalController { } - override func fullscreenDidChange() { - super.fullscreenDidChange() + override func fullscreenDidChange(mode: FullscreenMode, enabled: Bool) { + super.fullscreenDidChange(mode: mode, enabled: enabled) // When our fullscreen state changes, we resync our appearance because some // properties change when fullscreen or not. diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 65f928443..cd024eb2d 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -269,6 +269,10 @@ extension Ghostty.Notification { static let ghosttyToggleFullscreen = Notification.Name("com.mitchellh.ghostty.toggleFullscreen") static let FullscreenModeKey = ghosttyToggleFullscreen.rawValue + /// Toggle fullscreen of current window + static let ghosttyDidToggleFullscreen = Notification.Name("com.mitchellh.ghostty.didToggleFullscreen") + static let FullscreenEnabledKey = ghosttyDidToggleFullscreen.rawValue + ".bool" + /// Notification sent to toggle split maximize/unmaximize. static let didToggleSplitZoom = Notification.Name("com.mitchellh.ghostty.didToggleSplitZoom") diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 4abf87c7f..04f6f66a3 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -49,6 +49,9 @@ extension Ghostty { // Maintain whether our window has focus (is key) or not @State private var windowFocus: Bool = true + // Maintain whether our window is fullscreen or not + @State private var paddingTop: CGFloat = 0 + // True if we're hovering over the left URL view, so we can show it on the right. @State private var isHoveringURLLeft: Bool = false @@ -70,9 +73,11 @@ extension Ghostty { #if canImport(AppKit) let pubBecomeKey = center.publisher(for: NSWindow.didBecomeKeyNotification) let pubResign = center.publisher(for: NSWindow.didResignKeyNotification) + let pubFullscreen = center.publisher(for: Ghostty.Notification.ghosttyDidToggleFullscreen) #endif - Surface(view: surfaceView, size: geo.size) + let adjustedSize = CGSize(width: geo.size.width, height: geo.size.height - paddingTop) + Surface(view: surfaceView, size: adjustedSize) .focused($surfaceFocus) .focusedValue(\.ghosttySurfaceTitle, surfaceView.title) .focusedValue(\.ghosttySurfacePwd, surfaceView.pwd) @@ -80,6 +85,19 @@ extension Ghostty { .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) #if canImport(AppKit) .backport.pointerStyle(surfaceView.pointerStyle) + .onReceive(pubFullscreen) { notification in + guard let enabled = notification.userInfo?[Ghostty.Notification.FullscreenEnabledKey] as? Bool else { + return + } + guard let mode = notification.userInfo?[Ghostty.Notification.FullscreenModeKey] as? FullscreenMode else { + return + } + guard let window = notification.object as? NSWindow else { return } + guard let screen = window.screen else { + return + } + paddingTop = enabled && mode == FullscreenMode.nonNative ? screen.safeAreaInsets.top : 0; + } .onReceive(pubBecomeKey) { notification in guard let window = notification.object as? NSWindow else { return } guard let surfaceWindow = surfaceView.window else { return } @@ -92,6 +110,7 @@ extension Ghostty { windowFocus = false } } + .padding(.top, paddingTop) .onDrop(of: [.fileURL], isTargeted: nil) { providers in providers.forEach { provider in _ = provider.loadObject(ofClass: URL.self) { url, _ in @@ -125,6 +144,7 @@ extension Ghostty { .ghosttySurfaceView(surfaceView) #if canImport(AppKit) + // If we are in the middle of a key sequence, then we show a visual element. We only // support this on macOS currently although in theory we can support mobile with keyboards! if !surfaceView.keySequence.isEmpty { diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index a16f329f8..ce6b69a54 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -38,11 +38,11 @@ protocol FullscreenStyle { protocol FullscreenDelegate: AnyObject { /// Called whenever the fullscreen state changed. You can call isFullscreen to see /// the current state. - func fullscreenDidChange() + func fullscreenDidChange(mode: FullscreenMode, enabled: Bool) } extension FullscreenDelegate { - func fullscreenDidChange() {} + func fullscreenDidChange(mode: FullscreenMode, enabled: Bool) {} } /// The base class for fullscreen implementations, cannot be used as a FullscreenStyle on its own. @@ -74,11 +74,11 @@ class FullscreenBase { } @objc private func didEnterFullScreenNotification(_ notification: Notification) { - delegate?.fullscreenDidChange() + delegate?.fullscreenDidChange(mode: FullscreenMode.native, enabled: true) } @objc private func didExitFullScreenNotification(_ notification: Notification) { - delegate?.fullscreenDidChange() + delegate?.fullscreenDidChange(mode: FullscreenMode.native, enabled: false) } } @@ -202,7 +202,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // https://github.com/ghostty-org/ghostty/issues/1996 DispatchQueue.main.async { self.window.setFrame(self.fullscreenFrame(screen), display: true) - self.delegate?.fullscreenDidChange() + self.delegate?.fullscreenDidChange(mode: FullscreenMode.nonNative, enabled: true) } } @@ -258,7 +258,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { window.makeKeyAndOrderFront(nil) // Notify the delegate - self.delegate?.fullscreenDidChange() + self.delegate?.fullscreenDidChange(mode: FullscreenMode.nonNative, enabled: false) } private func fullscreenFrame(_ screen: NSScreen) -> NSRect {