mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-19 18:26:13 +03:00
macos: add titled-visible-menu option to macos-non-native-fullscreen
Non-native fullscreen has certain limitations at the moment regarding being truly fullscreen (taking all screen surface) and losing no functionality when activated. Currently, tab functionality is lost when non-native fullscreen is activated. This commit introduces the `titled-visible-menu` mode for macOS non-native fullscreen. Like `visible-menu` mode, it hides the dock and uses its surface, leaving the menubar visible. This mode makes full use of the screen (except for the menubar) while retaining the tabbar’s functionality. While a truly fullscreen non-native mode without feature loss is ideal, this implementation provides a functional alternative in the meantime.
This commit is contained in:
@ -515,6 +515,7 @@ typedef enum {
|
|||||||
GHOSTTY_FULLSCREEN_NON_NATIVE,
|
GHOSTTY_FULLSCREEN_NON_NATIVE,
|
||||||
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
|
GHOSTTY_FULLSCREEN_NON_NATIVE_VISIBLE_MENU,
|
||||||
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH,
|
||||||
|
GHOSTTY_FULLSCREEN_NON_NATIVE_TITLED_VISIBLE_MENU,
|
||||||
} ghostty_action_fullscreen_e;
|
} ghostty_action_fullscreen_e;
|
||||||
|
|
||||||
// apprt.action.FloatWindow
|
// apprt.action.FloatWindow
|
||||||
|
@ -775,13 +775,79 @@ class BaseTerminalController: NSWindowController,
|
|||||||
// We have no previous style
|
// We have no previous style
|
||||||
self.fullscreenStyle = newStyle
|
self.fullscreenStyle = newStyle
|
||||||
}
|
}
|
||||||
guard let fullscreenStyle else { return }
|
|
||||||
|
|
||||||
if fullscreenStyle.isFullscreen {
|
if let fullscreenStyle {
|
||||||
fullscreenStyle.exit()
|
if fullscreenStyle.isFullscreen {
|
||||||
} else {
|
fullscreenStyle.exit()
|
||||||
fullscreenStyle.enter()
|
} else {
|
||||||
|
fullscreenStyle.enter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let tabbedWindows = window.tabbedWindows {
|
||||||
|
for otherTabWindow in tabbedWindows {
|
||||||
|
if otherTabWindow != window,
|
||||||
|
let otherTabController = otherTabWindow.windowController as? BaseTerminalController
|
||||||
|
{
|
||||||
|
otherTabController.syncNonNativeTabbedFullscreenState(with: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window fullscreen state to match the given controller.
|
||||||
|
func syncNonNativeTabbedFullscreenState(with controller: BaseTerminalController) {
|
||||||
|
// We need a window to sync its fullscreen-ness
|
||||||
|
guard let window = self.window else { return }
|
||||||
|
|
||||||
|
// If the target fullscreen style is not a non-native titled
|
||||||
|
// fullscreen style, we are not interested in syncing.
|
||||||
|
guard let targetFullscreenStyle = controller.fullscreenStyle as? NonNativeFullscreen else { return }
|
||||||
|
if !targetFullscreenStyle.properties.titled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !targetFullscreenStyle.isFullscreen {
|
||||||
|
if let oldStyle = self.fullscreenStyle as? NonNativeFullscreen, oldStyle.isFullscreen {
|
||||||
|
oldStyle.exit(shouldFocus: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let controllerWindow = controller.window else { return }
|
||||||
|
window.setFrame(controllerWindow.frame, display: true)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let oldStyle = self.fullscreenStyle, oldStyle.isFullscreen {
|
||||||
|
if type(of: oldStyle) == type(of: targetFullscreenStyle) {
|
||||||
|
// If the styles are the same and the old style is already in fullscreen mode
|
||||||
|
// we don't need to do anything.
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// If it's a different type of fullscreen style, exit fullscreen first
|
||||||
|
oldStyle.exit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point `targetFullscreenStyle` is `NonNativeFullscreen`, so we know
|
||||||
|
// `newStyle` will be too, so the next line is solely for the sake of keeping
|
||||||
|
// the type-checker happy
|
||||||
|
guard let newStyle = targetFullscreenStyle.fullscreenMode.style(for: window) as? NonNativeFullscreen else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newStyle.delegate = self
|
||||||
|
self.fullscreenStyle = newStyle
|
||||||
|
|
||||||
|
newStyle.enter(shouldFocus: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are mostly hacks and patches, required to keep the behavior of the tab-supporting
|
||||||
|
// non-native fullscreen style consistent
|
||||||
|
func reapplyNonNativeTabbedFullscreen() {
|
||||||
|
// If the target fullscreen style is not a non-native fullscreen style,
|
||||||
|
// we are not interested in reapplying fullscreen style.
|
||||||
|
guard let fullscreenStyle = fullscreenStyle as? NonNativeFullscreen else { return }
|
||||||
|
fullscreenStyle.reapply()
|
||||||
}
|
}
|
||||||
|
|
||||||
func fullscreenDidChange() {}
|
func fullscreenDidChange() {}
|
||||||
@ -921,6 +987,9 @@ class BaseTerminalController: NSWindowController,
|
|||||||
// Becoming/losing key means we have to notify our surface(s) that we have focus
|
// Becoming/losing key means we have to notify our surface(s) that we have focus
|
||||||
// so things like cursors blink, pty events are sent, etc.
|
// so things like cursors blink, pty events are sent, etc.
|
||||||
self.syncFocusToSurfaceTree()
|
self.syncFocusToSurfaceTree()
|
||||||
|
// Some non-native fullscreen modes are displaced/repositioned when losing/taking
|
||||||
|
// focus. So we try to fix their position/size whenever the window takes focus.
|
||||||
|
self.reapplyNonNativeTabbedFullscreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
func windowDidResignKey(_ notification: Notification) {
|
func windowDidResignKey(_ notification: Notification) {
|
||||||
|
@ -204,7 +204,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
// fullscreen for the logic later in this method.
|
// fullscreen for the logic later in this method.
|
||||||
c.toggleFullscreen(mode: .native)
|
c.toggleFullscreen(mode: .native)
|
||||||
|
|
||||||
case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch:
|
case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch, .nonNativeTitledVisibleMenu:
|
||||||
// If we're non-native then we have to do it on a later loop
|
// If we're non-native then we have to do it on a later loop
|
||||||
// so that the content view is setup.
|
// so that the content view is setup.
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
@ -268,7 +268,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
return newWindow(ghostty, withBaseConfig: baseConfig, withParent: parent)
|
return newWindow(ghostty, withBaseConfig: baseConfig, withParent: parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If our parent is in non-native fullscreen, then new tabs do not work.
|
// If our parent is in non-native fullscreen, not supporting tabs,
|
||||||
|
// then new tabs do not work.
|
||||||
// See: https://github.com/mitchellh/ghostty/issues/392
|
// See: https://github.com/mitchellh/ghostty/issues/392
|
||||||
if let fullscreenStyle = parentController.fullscreenStyle,
|
if let fullscreenStyle = parentController.fullscreenStyle,
|
||||||
fullscreenStyle.isFullscreen && !fullscreenStyle.supportsTabs {
|
fullscreenStyle.isFullscreen && !fullscreenStyle.supportsTabs {
|
||||||
@ -285,6 +286,10 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||||||
let controller = TerminalController.init(ghostty, withBaseConfig: baseConfig)
|
let controller = TerminalController.init(ghostty, withBaseConfig: baseConfig)
|
||||||
guard let window = controller.window else { return controller }
|
guard let window = controller.window else { return controller }
|
||||||
|
|
||||||
|
// Non-native tab-supporting fullscreen styles should be manually synced.
|
||||||
|
// It will be a no-op if no sync is needed.
|
||||||
|
controller.syncNonNativeTabbedFullscreenState(with: parentController)
|
||||||
|
|
||||||
// If the parent is miniaturized, then macOS exhibits really strange behaviors
|
// If the parent is miniaturized, then macOS exhibits really strange behaviors
|
||||||
// so we have to bring it back out.
|
// so we have to bring it back out.
|
||||||
if (parent.isMiniaturized) { parent.deminiaturize(self) }
|
if (parent.isMiniaturized) { parent.deminiaturize(self) }
|
||||||
|
@ -23,7 +23,26 @@ class TerminalWindow: NSWindow {
|
|||||||
windowController as? TerminalController
|
windowController as? TerminalController
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the window has frame-react constraints applied.
|
||||||
|
var frameRectConstrained: Bool = false {
|
||||||
|
didSet {
|
||||||
|
// If we set this to true, then we need to ensure that the frame is
|
||||||
|
// within the constraints.
|
||||||
|
if frameRectConstrained {
|
||||||
|
setFrame(frame, display: true, animate: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: NSWindow Overrides
|
// MARK: NSWindow Overrides
|
||||||
|
override func constrainFrameRect(_ frameRect: NSRect,
|
||||||
|
to screen: NSScreen?) -> NSRect {
|
||||||
|
if (frameRectConstrained) {
|
||||||
|
return super.constrainFrameRect(frameRect, to: screen)
|
||||||
|
} else {
|
||||||
|
return frameRect
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override var toolbar: NSToolbar? {
|
override var toolbar: NSToolbar? {
|
||||||
didSet {
|
didSet {
|
||||||
@ -446,7 +465,7 @@ extension TerminalWindow {
|
|||||||
struct ResetZoomAccessoryView: View {
|
struct ResetZoomAccessoryView: View {
|
||||||
@ObservedObject var viewModel: ViewModel
|
@ObservedObject var viewModel: ViewModel
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
// The padding from the top that the view appears. This was all just manually
|
// The padding from the top that the view appears. This was all just manually
|
||||||
// measured based on the OS.
|
// measured based on the OS.
|
||||||
var topPadding: CGFloat {
|
var topPadding: CGFloat {
|
||||||
|
@ -16,6 +16,9 @@ extension FullscreenMode {
|
|||||||
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
|
case GHOSTTY_FULLSCREEN_NON_NATIVE_PADDED_NOTCH:
|
||||||
.nonNativePaddedNotch
|
.nonNativePaddedNotch
|
||||||
|
|
||||||
|
case GHOSTTY_FULLSCREEN_NON_NATIVE_TITLED_VISIBLE_MENU:
|
||||||
|
.nonNativeTitledVisibleMenu
|
||||||
|
|
||||||
default:
|
default:
|
||||||
nil
|
nil
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ extension Ghostty {
|
|||||||
let key = "window-position-x"
|
let key = "window-position-x"
|
||||||
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
|
return ghostty_config_get(config, &v, key, UInt(key.count)) ? v : nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var windowPositionY: Int16? {
|
var windowPositionY: Int16? {
|
||||||
guard let config = self.config else { return nil }
|
guard let config = self.config else { return nil }
|
||||||
var v: Int16 = 0
|
var v: Int16 = 0
|
||||||
@ -235,6 +235,8 @@ extension Ghostty {
|
|||||||
.nonNativeVisibleMenu
|
.nonNativeVisibleMenu
|
||||||
case "padded-notch":
|
case "padded-notch":
|
||||||
.nonNativePaddedNotch
|
.nonNativePaddedNotch
|
||||||
|
case "titled-visible-menu":
|
||||||
|
.nonNativeTitledVisibleMenu
|
||||||
default:
|
default:
|
||||||
defaultValue
|
defaultValue
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ enum FullscreenMode {
|
|||||||
case nonNative
|
case nonNative
|
||||||
case nonNativeVisibleMenu
|
case nonNativeVisibleMenu
|
||||||
case nonNativePaddedNotch
|
case nonNativePaddedNotch
|
||||||
|
case nonNativeTitledVisibleMenu
|
||||||
|
|
||||||
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
|
/// Initializes the fullscreen style implementation for the mode. This will not toggle any
|
||||||
/// fullscreen properties. This may fail if the window isn't configured properly for a given
|
/// fullscreen properties. This may fail if the window isn't configured properly for a given
|
||||||
@ -19,11 +20,14 @@ enum FullscreenMode {
|
|||||||
case .nonNative:
|
case .nonNative:
|
||||||
return NonNativeFullscreen(window)
|
return NonNativeFullscreen(window)
|
||||||
|
|
||||||
case .nonNativeVisibleMenu:
|
case .nonNativeVisibleMenu:
|
||||||
return NonNativeFullscreenVisibleMenu(window)
|
return NonNativeFullscreenVisibleMenu(window)
|
||||||
|
|
||||||
case .nonNativePaddedNotch:
|
case .nonNativePaddedNotch:
|
||||||
return NonNativeFullscreenPaddedNotch(window)
|
return NonNativeFullscreenPaddedNotch(window)
|
||||||
|
|
||||||
|
case .nonNativeTitledVisibleMenu:
|
||||||
|
return NonNativeTitledFullscreenVisibleMenu(window)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,6 +37,7 @@ protocol FullscreenStyle {
|
|||||||
var delegate: FullscreenDelegate? { get set }
|
var delegate: FullscreenDelegate? { get set }
|
||||||
var isFullscreen: Bool { get }
|
var isFullscreen: Bool { get }
|
||||||
var supportsTabs: Bool { get }
|
var supportsTabs: Bool { get }
|
||||||
|
var fullscreenMode: FullscreenMode { get }
|
||||||
init?(_ window: NSWindow)
|
init?(_ window: NSWindow)
|
||||||
func enter()
|
func enter()
|
||||||
func exit()
|
func exit()
|
||||||
@ -89,6 +94,7 @@ class FullscreenBase {
|
|||||||
class NativeFullscreen: FullscreenBase, FullscreenStyle {
|
class NativeFullscreen: FullscreenBase, FullscreenStyle {
|
||||||
var isFullscreen: Bool { window.styleMask.contains(.fullScreen) }
|
var isFullscreen: Bool { window.styleMask.contains(.fullScreen) }
|
||||||
var supportsTabs: Bool { true }
|
var supportsTabs: Bool { true }
|
||||||
|
var fullscreenMode: FullscreenMode { .native }
|
||||||
|
|
||||||
required init?(_ window: NSWindow) {
|
required init?(_ window: NSWindow) {
|
||||||
// TODO: There are many requirements for native fullscreen we should
|
// TODO: There are many requirements for native fullscreen we should
|
||||||
@ -130,6 +136,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
// Non-native fullscreen never supports tabs because tabs require
|
// Non-native fullscreen never supports tabs because tabs require
|
||||||
// the "titled" style and we don't have it for non-native fullscreen.
|
// the "titled" style and we don't have it for non-native fullscreen.
|
||||||
var supportsTabs: Bool { false }
|
var supportsTabs: Bool { false }
|
||||||
|
var fullscreenMode: FullscreenMode { .nonNative }
|
||||||
|
|
||||||
// isFullscreen is dependent on if we have saved state currently. We
|
// isFullscreen is dependent on if we have saved state currently. We
|
||||||
// could one day try to do fancier stuff like inspecting the window
|
// could one day try to do fancier stuff like inspecting the window
|
||||||
@ -143,6 +150,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
|
|
||||||
struct Properties {
|
struct Properties {
|
||||||
var hideMenu: Bool = true
|
var hideMenu: Bool = true
|
||||||
|
var titled: Bool = false
|
||||||
var paddedNotch: Bool = false
|
var paddedNotch: Bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +177,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func enter() {
|
func enter() {
|
||||||
|
enter(shouldFocus: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enter(shouldFocus: Bool = true) {
|
||||||
// If we are in fullscreen we don't do it again.
|
// If we are in fullscreen we don't do it again.
|
||||||
guard !isFullscreen else { return }
|
guard !isFullscreen else { return }
|
||||||
|
|
||||||
@ -216,15 +228,20 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
name: NSWindow.didChangeScreenNotification,
|
name: NSWindow.didChangeScreenNotification,
|
||||||
object: window)
|
object: window)
|
||||||
|
|
||||||
// Being untitled let's our content take up the full frame.
|
if (!properties.titled) {
|
||||||
window.styleMask.remove(.titled)
|
// Being untitled let's our content take up the full frame.
|
||||||
|
window.styleMask.remove(.titled)
|
||||||
|
}
|
||||||
|
|
||||||
// We dont' want the non-native fullscreen window to be resizable
|
// We dont' want the non-native fullscreen window to be resizable
|
||||||
// from the edges.
|
// from the edges.
|
||||||
window.styleMask.remove(.resizable)
|
window.styleMask.remove(.resizable)
|
||||||
|
|
||||||
// Focus window
|
if (shouldFocus) {
|
||||||
window.makeKeyAndOrderFront(nil)
|
// If we are entering fullscreen, we want to focus the window
|
||||||
|
// so that it is the key window.
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Set frame to screen size, accounting for any elements such as the menu bar.
|
// Set frame to screen size, accounting for any elements such as the menu bar.
|
||||||
// We do this async so that all the style edits above (title removal, dock
|
// We do this async so that all the style edits above (title removal, dock
|
||||||
@ -242,6 +259,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func exit() {
|
func exit() {
|
||||||
|
exit(shouldFocus: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exit(shouldFocus: Bool = true) {
|
||||||
guard isFullscreen else { return }
|
guard isFullscreen else { return }
|
||||||
guard let savedState else { return }
|
guard let savedState else { return }
|
||||||
|
|
||||||
@ -254,12 +275,17 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
let firstResponder = window.firstResponder
|
let firstResponder = window.firstResponder
|
||||||
|
|
||||||
// Unhide our elements
|
// Unhide our elements
|
||||||
if savedState.dock {
|
if (savedState.dock) {
|
||||||
unhideDock()
|
unhideDock()
|
||||||
}
|
}
|
||||||
if (properties.hideMenu && savedState.menu) {
|
if (properties.hideMenu && savedState.menu) {
|
||||||
unhideMenu()
|
unhideMenu()
|
||||||
}
|
}
|
||||||
|
if let window = window as? TerminalWindow {
|
||||||
|
// If we are a TerminalWindow, we need to restore the frameRectConstrained
|
||||||
|
// property so that the window can be resized again.
|
||||||
|
window.frameRectConstrained = savedState.frameRectConstrained
|
||||||
|
}
|
||||||
|
|
||||||
// Restore our saved state
|
// Restore our saved state
|
||||||
window.styleMask = savedState.styleMask
|
window.styleMask = savedState.styleMask
|
||||||
@ -273,7 +299,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
if let window = window as? TerminalWindow, window.isTabBar(c) {
|
if let window = window as? TerminalWindow, window.isTabBar(c) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if window.titlebarAccessoryViewControllers.firstIndex(of: c) == nil {
|
if window.titlebarAccessoryViewControllers.firstIndex(of: c) == nil {
|
||||||
window.addTitlebarAccessoryViewController(c)
|
window.addTitlebarAccessoryViewController(c)
|
||||||
}
|
}
|
||||||
@ -282,23 +308,26 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
// Removing "titled" also clears our toolbar
|
// Removing "titled" also clears our toolbar
|
||||||
window.toolbar = savedState.toolbar
|
window.toolbar = savedState.toolbar
|
||||||
window.toolbarStyle = savedState.toolbarStyle
|
window.toolbarStyle = savedState.toolbarStyle
|
||||||
|
|
||||||
// If the window was previously in a tab group that isn't empty now,
|
if (!properties.titled) {
|
||||||
// we re-add it. We have to do this because our process of doing non-native
|
// If the window was previously in a tab group that isn't empty now,
|
||||||
// fullscreen removes the window from the tab group.
|
// we re-add it. We have to do this because our process of doing non-native
|
||||||
if let tabGroup = savedState.tabGroup,
|
// fullscreen removes the window from the tab group.
|
||||||
let tabIndex = savedState.tabGroupIndex,
|
if let tabGroup = savedState.tabGroup,
|
||||||
!tabGroup.windows.isEmpty {
|
let tabIndex = savedState.tabGroupIndex,
|
||||||
if tabIndex == 0 {
|
!tabGroup.windows.isEmpty
|
||||||
// We were previously the first tab. Add it before ("below")
|
{
|
||||||
// the first window in the tab group currently.
|
if tabIndex == 0 {
|
||||||
tabGroup.windows.first!.addTabbedWindow(window, ordered: .below)
|
// We were previously the first tab. Add it before ("below")
|
||||||
} else if tabIndex <= tabGroup.windows.count {
|
// the first window in the tab group currently.
|
||||||
// We were somewhere in the middle
|
tabGroup.windows.first!.addTabbedWindow(window, ordered: .below)
|
||||||
tabGroup.windows[tabIndex - 1].addTabbedWindow(window, ordered: .above)
|
} else if tabIndex <= tabGroup.windows.count {
|
||||||
} else {
|
// We were somewhere in the middle
|
||||||
// We were at the end
|
tabGroup.windows[tabIndex - 1].addTabbedWindow(window, ordered: .above)
|
||||||
tabGroup.windows.last!.addTabbedWindow(window, ordered: .below)
|
} else {
|
||||||
|
// We were at the end
|
||||||
|
tabGroup.windows.last!.addTabbedWindow(window, ordered: .below)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,14 +338,33 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
// Unset our saved state, we're restored!
|
// Unset our saved state, we're restored!
|
||||||
self.savedState = nil
|
self.savedState = nil
|
||||||
|
|
||||||
// Focus window
|
if (shouldFocus) {
|
||||||
window.makeKeyAndOrderFront(nil)
|
// Focus window
|
||||||
|
window.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Notify the delegate
|
// Notify the delegate
|
||||||
NotificationCenter.default.post(name: .fullscreenDidExit, object: self)
|
NotificationCenter.default.post(name: .fullscreenDidExit, object: self)
|
||||||
self.delegate?.fullscreenDidChange()
|
self.delegate?.fullscreenDidChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some of the tweaks we do to the window in non-native fullscreen need to reapply
|
||||||
|
// after specific events, like regaining focus. This is specially the case for tab
|
||||||
|
// supporting styles which use unofficial api of macOS.
|
||||||
|
func reapply() {
|
||||||
|
if !self.isFullscreen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.properties.titled {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
guard let screen = self.window.screen else { return }
|
||||||
|
|
||||||
|
self.window.setFrame(self.fullscreenFrame(screen), display: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func fullscreenFrame(_ screen: NSScreen) -> NSRect {
|
private func fullscreenFrame(_ screen: NSScreen) -> NSRect {
|
||||||
// It would make more sense to use "visibleFrame" but visibleFrame
|
// It would make more sense to use "visibleFrame" but visibleFrame
|
||||||
// will omit space by our dock and isn't updated until an event
|
// will omit space by our dock and isn't updated until an event
|
||||||
@ -396,6 +444,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
let titlebarAccessoryViewControllers: [NSTitlebarAccessoryViewController]
|
let titlebarAccessoryViewControllers: [NSTitlebarAccessoryViewController]
|
||||||
let dock: Bool
|
let dock: Bool
|
||||||
let menu: Bool
|
let menu: Bool
|
||||||
|
let frameRectConstrained: Bool
|
||||||
|
|
||||||
init?(_ window: NSWindow) {
|
init?(_ window: NSWindow) {
|
||||||
guard let contentView = window.contentView else { return nil }
|
guard let contentView = window.contentView else { return nil }
|
||||||
@ -409,6 +458,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
self.toolbarStyle = window.toolbarStyle
|
self.toolbarStyle = window.toolbarStyle
|
||||||
self.titlebarAccessoryViewControllers = window.titlebarAccessoryViewControllers
|
self.titlebarAccessoryViewControllers = window.titlebarAccessoryViewControllers
|
||||||
self.dock = window.screen?.hasDock ?? false
|
self.dock = window.screen?.hasDock ?? false
|
||||||
|
self.frameRectConstrained = (window as? TerminalWindow)?.frameRectConstrained ?? false
|
||||||
|
|
||||||
if let cgWindowId = window.cgWindowId {
|
if let cgWindowId = window.cgWindowId {
|
||||||
// We hide the menu only if this window is not on any fullscreen
|
// We hide the menu only if this window is not on any fullscreen
|
||||||
@ -434,10 +484,18 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle {
|
|||||||
|
|
||||||
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
|
class NonNativeFullscreenVisibleMenu: NonNativeFullscreen {
|
||||||
override var properties: Properties { Properties(hideMenu: false) }
|
override var properties: Properties { Properties(hideMenu: false) }
|
||||||
|
override var fullscreenMode: FullscreenMode { .nonNativeVisibleMenu }
|
||||||
}
|
}
|
||||||
|
|
||||||
class NonNativeFullscreenPaddedNotch: NonNativeFullscreen {
|
class NonNativeFullscreenPaddedNotch: NonNativeFullscreen {
|
||||||
override var properties: Properties { Properties(paddedNotch: true) }
|
override var properties: Properties { Properties(paddedNotch: true) }
|
||||||
|
override var fullscreenMode: FullscreenMode { .nonNativePaddedNotch }
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonNativeTitledFullscreenVisibleMenu: NonNativeFullscreen {
|
||||||
|
override var supportsTabs: Bool { true }
|
||||||
|
override var properties: Properties { Properties(titled: true) }
|
||||||
|
override var fullscreenMode: FullscreenMode { .nonNativeTitledVisibleMenu }
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Notification.Name {
|
extension Notification.Name {
|
||||||
|
@ -4760,6 +4760,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
.true => .macos_non_native,
|
.true => .macos_non_native,
|
||||||
.@"visible-menu" => .macos_non_native_visible_menu,
|
.@"visible-menu" => .macos_non_native_visible_menu,
|
||||||
.@"padded-notch" => .macos_non_native_padded_notch,
|
.@"padded-notch" => .macos_non_native_padded_notch,
|
||||||
|
.@"titled-visible-menu" => .macos_non_native_titled_visible_menu,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -458,6 +458,7 @@ pub const Fullscreen = enum(c_int) {
|
|||||||
macos_non_native,
|
macos_non_native,
|
||||||
macos_non_native_visible_menu,
|
macos_non_native_visible_menu,
|
||||||
macos_non_native_padded_notch,
|
macos_non_native_padded_notch,
|
||||||
|
macos_non_native_titled_visible_menu,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FloatWindow = enum(c_int) {
|
pub const FloatWindow = enum(c_int) {
|
||||||
|
@ -2505,6 +2505,8 @@ keybind: Keybinds = .{},
|
|||||||
/// * `false` - Use native macOS fullscreen
|
/// * `false` - Use native macOS fullscreen
|
||||||
/// * `visible-menu` - Use non-native macOS fullscreen, keep the menu bar
|
/// * `visible-menu` - Use non-native macOS fullscreen, keep the menu bar
|
||||||
/// visible
|
/// visible
|
||||||
|
/// * `titled-visible-menu` - Use non-native macOS fullscreen, keep the menu
|
||||||
|
/// bar and title bar visible
|
||||||
/// * `padded-notch` - Use non-native macOS fullscreen, hide the menu bar,
|
/// * `padded-notch` - Use non-native macOS fullscreen, hide the menu bar,
|
||||||
/// but ensure the window is not obscured by the notch on applicable
|
/// but ensure the window is not obscured by the notch on applicable
|
||||||
/// devices. The area around the notch will remain transparent currently,
|
/// devices. The area around the notch will remain transparent currently,
|
||||||
@ -4471,6 +4473,7 @@ pub const NonNativeFullscreen = enum(c_int) {
|
|||||||
true,
|
true,
|
||||||
@"visible-menu",
|
@"visible-menu",
|
||||||
@"padded-notch",
|
@"padded-notch",
|
||||||
|
@"titled-visible-menu",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Valid values for macos-option-as-alt.
|
/// Valid values for macos-option-as-alt.
|
||||||
|
Reference in New Issue
Block a user