diff --git a/include/ghostty.h b/include/ghostty.h index b32cc9856..ae41429de 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -509,6 +509,15 @@ typedef struct { ghostty_quick_terminal_size_s secondary; } ghostty_config_quick_terminal_size_s; +// config.Fullscreen +typedef enum { + GHOSTTY_CONFIG_FULLSCREEN_FALSE, + GHOSTTY_CONFIG_FULLSCREEN_TRUE, + GHOSTTY_CONFIG_FULLSCREEN_NON_NATIVE, + GHOSTTY_CONFIG_FULLSCREEN_NON_NATIVE_VISIBLE_MENU, + GHOSTTY_CONFIG_FULLSCREEN_NON_NATIVE_PADDED_NOTCH, +} ghostty_config_fullscreen_e; + // apprt.Target.Key typedef enum { GHOSTTY_TARGET_APP, diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index c7f9fe086..fc23cc28f 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -224,27 +224,25 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr // otherwise the focused terminal, otherwise an arbitrary one. let parent: NSWindow? = explicitParent ?? preferredParent?.window - if let parent { - if parent.styleMask.contains(.fullScreen) { - // If our previous window was fullscreen then we want our new window to - // be fullscreen. This behavior actually doesn't match the native tabbing - // behavior of macOS apps where new windows create tabs when in native - // fullscreen but this is how we've always done it. This matches iTerm2 - // behavior. + if let parent, parent.styleMask.contains(.fullScreen) { + // If our previous window was fullscreen then we want our new window to + // be fullscreen. This behavior actually doesn't match the native tabbing + // behavior of macOS apps where new windows create tabs when in native + // fullscreen but this is how we've always done it. This matches iTerm2 + // behavior. + c.toggleFullscreen(mode: .native) + } else if let fullscreenMode = ghostty.config.windowFullscreen { + switch fullscreenMode { + case .native: + // Native has to be done immediately so that our stylemask contains + // fullscreen for the logic later in this method. c.toggleFullscreen(mode: .native) - } else if ghostty.config.windowFullscreen { - switch (ghostty.config.windowFullscreenMode) { - case .native: - // Native has to be done immediately so that our stylemask contains - // fullscreen for the logic later in this method. - c.toggleFullscreen(mode: .native) - case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch: - // If we're non-native then we have to do it on a later loop - // so that the content view is setup. - DispatchQueue.main.async { - c.toggleFullscreen(mode: ghostty.config.windowFullscreenMode) - } + case .nonNative, .nonNativeVisibleMenu, .nonNativePaddedNotch: + // If we're non-native then we have to do it on a later loop + // so that the content view is setup. + DispatchQueue.main.async { + c.toggleFullscreen(mode: fullscreenMode) } } } diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index c64646e25..55e289a3c 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -227,14 +227,46 @@ extension Ghostty { return v } - var windowFullscreen: Bool { - guard let config = self.config else { return true } - var v = false + /// Returns the fullscreen mode if fullscreen is enabled, or nil if disabled. + /// This parses the `fullscreen` enum config which supports both + /// native and non-native fullscreen modes. + #if canImport(AppKit) + var windowFullscreen: FullscreenMode? { + guard let config = self.config else { return nil } + var v: UnsafePointer? = nil let key = "fullscreen" - _ = ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) - return v + guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return nil } + guard let ptr = v else { return nil } + let str = String(cString: ptr) + return switch str { + case "false": + nil + case "true": + .native + case "non-native": + .nonNative + case "non-native-visible-menu": + .nonNativeVisibleMenu + case "non-native-padded-notch": + .nonNativePaddedNotch + default: + nil + } } + #else + var windowFullscreen: Bool { + guard let config = self.config else { return false } + var v: UnsafePointer? = nil + let key = "fullscreen" + guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return false } + guard let ptr = v else { return false } + let str = String(cString: ptr) + return str != "false" + } + #endif + /// Returns the fullscreen mode for toggle actions (keybindings). + /// This is controlled by `macos-non-native-fullscreen` config. #if canImport(AppKit) var windowFullscreenMode: FullscreenMode { let defaultValue: FullscreenMode = .native diff --git a/src/Surface.zig b/src/Surface.zig index fbb4d9119..b9dbefa1b 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -312,6 +312,7 @@ const DerivedConfig = struct { mouse_reporting: bool, mouse_scroll_multiplier: configpkg.MouseScrollMultiplier, mouse_shift_capture: configpkg.MouseShiftCapture, + fullscreen: configpkg.Fullscreen, macos_non_native_fullscreen: configpkg.NonNativeFullscreen, macos_option_as_alt: ?input.OptionAsAlt, selection_clear_on_copy: bool, @@ -389,6 +390,7 @@ const DerivedConfig = struct { .mouse_reporting = config.@"mouse-reporting", .mouse_scroll_multiplier = config.@"mouse-scroll-multiplier", .mouse_shift_capture = config.@"mouse-shift-capture", + .fullscreen = config.fullscreen, .macos_non_native_fullscreen = config.@"macos-non-native-fullscreen", .macos_option_as_alt = config.@"macos-option-as-alt", .selection_clear_on_copy = config.@"selection-clear-on-copy", diff --git a/src/apprt/gtk/class/window.zig b/src/apprt/gtk/class/window.zig index f96bccd64..543080394 100644 --- a/src/apprt/gtk/class/window.zig +++ b/src/apprt/gtk/class/window.zig @@ -308,7 +308,7 @@ pub const Window = extern struct { if (priv.config) |config_obj| { const config = config_obj.get(); if (config.maximize) self.as(gtk.Window).maximize(); - if (config.fullscreen) self.as(gtk.Window).fullscreen(); + if (config.fullscreen != .false) self.as(gtk.Window).fullscreen(); // If we have an explicit title set, we set that immediately // so that any applications inspecting the window states see diff --git a/src/config.zig b/src/config.zig index 4abd319a6..0bf61a47f 100644 --- a/src/config.zig +++ b/src/config.zig @@ -31,6 +31,7 @@ pub const Keybinds = Config.Keybinds; pub const MouseShiftCapture = Config.MouseShiftCapture; pub const MouseScrollMultiplier = Config.MouseScrollMultiplier; pub const NonNativeFullscreen = Config.NonNativeFullscreen; +pub const Fullscreen = Config.Fullscreen; pub const RepeatableCodepointMap = Config.RepeatableCodepointMap; pub const RepeatableFontVariation = Config.RepeatableFontVariation; pub const RepeatableString = Config.RepeatableString; diff --git a/src/config/Config.zig b/src/config/Config.zig index d409fb6fa..8a68bce4a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1428,10 +1428,27 @@ maximize: bool = false, /// does not apply to tabs, splits, etc. However, this setting will apply to all /// new windows, not just the first one. /// -/// On macOS, this setting does not work if window-decoration is set to -/// "none", because native fullscreen on macOS requires window decorations -/// to be set. -fullscreen: bool = false, +/// Allowable values are: +/// +/// * `false` - Don't start in fullscreen (default) +/// * `true` - Start in native fullscreen +/// * `non-native` - (macOS only) Start in non-native fullscreen, hiding the +/// menu bar. This is faster than native fullscreen since it doesn't use +/// animations. On non-macOS platforms, this behaves the same as `true`. +/// * `non-native-visible-menu` - (macOS only) Start in non-native fullscreen, +/// keeping the menu bar visible. On non-macOS platforms, behaves like `true`. +/// * `non-native-padded-notch` - (macOS only) Start in non-native fullscreen, +/// hiding the menu bar but padding for the notch on applicable devices. +/// On non-macOS platforms, behaves like `true`. +/// +/// Important: tabs DO NOT WORK with non-native fullscreen modes. Non-native +/// fullscreen removes the titlebar and macOS native tabs require the titlebar. +/// If you use tabs, use `true` (native) instead. +/// +/// On macOS, `true` (native fullscreen) does not work if `window-decoration` +/// is set to `false`, because native fullscreen on macOS requires window +/// decorations. +fullscreen: Fullscreen = .false, /// The title Ghostty will use for the window. This will force the title of the /// window to be this title at all times and Ghostty will ignore any set title @@ -5136,6 +5153,17 @@ pub const NonNativeFullscreen = enum(c_int) { @"padded-notch", }; +/// Valid values for fullscreen config option +/// c_int because it needs to be extern compatible +/// If this is changed, you must also update ghostty.h +pub const Fullscreen = enum(c_int) { + false, + true, + @"non-native", + @"non-native-visible-menu", + @"non-native-padded-notch", +}; + pub const WindowPaddingColor = enum { background, extend,