Merge pull request #443 from mitchellh/hide-decorations-macos

macos: allow to hide decorations
This commit is contained in:
Mitchell Hashimoto
2023-09-17 12:07:13 -07:00
committed by GitHub
4 changed files with 102 additions and 73 deletions

View File

@ -19,7 +19,7 @@ class PrimaryWindow: NSWindow {
static func create(ghostty: Ghostty.AppState, appDelegate: AppDelegate, baseConfig: ghostty_surface_config_s? = nil) -> PrimaryWindow { static func create(ghostty: Ghostty.AppState, appDelegate: AppDelegate, baseConfig: ghostty_surface_config_s? = nil) -> PrimaryWindow {
let window = PrimaryWindow( let window = PrimaryWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
styleMask: [.titled, .closable, .miniaturizable, .resizable], styleMask: getStyleMask(renderDecoration: ghostty.windowDecorations),
backing: .buffered, backing: .buffered,
defer: false) defer: false)
window.center() window.center()
@ -45,6 +45,15 @@ class PrimaryWindow: NSWindow {
return window return window
} }
static func getStyleMask(renderDecoration: Bool) -> NSWindow.StyleMask {
var mask: NSWindow.StyleMask = [.resizable, .closable, .miniaturizable]
if renderDecoration {
mask.insert(.titled)
}
return mask
}
override var canBecomeKey: Bool { override var canBecomeKey: Bool {
return true return true
} }

View File

@ -10,21 +10,21 @@ extension Ghostty {
enum AppReadiness { enum AppReadiness {
case loading, error, ready case loading, error, ready
} }
struct Info { struct Info {
var mode: ghostty_build_mode_e var mode: ghostty_build_mode_e
var version: String var version: String
} }
/// The AppState is the global state that is associated with the Swift app. This handles initially /// The AppState is the global state that is associated with the Swift app. This handles initially
/// initializing Ghostty, loading the configuration, etc. /// initializing Ghostty, loading the configuration, etc.
class AppState: ObservableObject { class AppState: ObservableObject {
/// The readiness value of the state. /// The readiness value of the state.
@Published var readiness: AppReadiness = .loading @Published var readiness: AppReadiness = .loading
/// Optional delegate /// Optional delegate
weak var delegate: GhosttyAppStateDelegate? weak var delegate: GhosttyAppStateDelegate?
/// The ghostty global configuration. This should only be changed when it is definitely /// The ghostty global configuration. This should only be changed when it is definitely
/// safe to change. It is definite safe to change only when the embedded app runtime /// safe to change. It is definite safe to change only when the embedded app runtime
/// in Ghostty says so (usually, only in a reload configuration callback). /// in Ghostty says so (usually, only in a reload configuration callback).
@ -35,7 +35,7 @@ extension Ghostty {
ghostty_config_free(old) ghostty_config_free(old)
} }
} }
/// The ghostty app instance. We only have one of these for the entire app, although I guess /// The ghostty app instance. We only have one of these for the entire app, although I guess
/// in theory you can have multiple... I don't know why you would... /// in theory you can have multiple... I don't know why you would...
@Published var app: ghostty_app_t? = nil { @Published var app: ghostty_app_t? = nil {
@ -44,13 +44,13 @@ extension Ghostty {
ghostty_app_free(old) ghostty_app_free(old)
} }
} }
/// True if we need to confirm before quitting. /// True if we need to confirm before quitting.
var needsConfirmQuit: Bool { var needsConfirmQuit: Bool {
guard let app = app else { return false } guard let app = app else { return false }
return ghostty_app_needs_confirm_quit(app) return ghostty_app_needs_confirm_quit(app)
} }
/// Build information /// Build information
var info: Info { var info: Info {
let raw = ghostty_info() let raw = ghostty_info()
@ -59,10 +59,19 @@ extension Ghostty {
length: Int(raw.version_len), length: Int(raw.version_len),
encoding: NSUTF8StringEncoding encoding: NSUTF8StringEncoding
) ?? "unknown" ) ?? "unknown"
return Info(mode: raw.build_mode, version: String(version)) return Info(mode: raw.build_mode, version: String(version))
} }
/// True if we want to render window decorations
var windowDecorations: Bool {
guard let config = self.config else { return true }
var v = false;
let key = "window-decoration"
_ = ghostty_config_get(config, &v, key, UInt(key.count))
return v;
}
/// Cached clipboard string for `read_clipboard` callback. /// Cached clipboard string for `read_clipboard` callback.
private var cached_clipboard_string: String? = nil private var cached_clipboard_string: String? = nil
@ -73,14 +82,14 @@ extension Ghostty {
readiness = .error readiness = .error
return return
} }
// Initialize the global configuration. // Initialize the global configuration.
guard let cfg = Self.loadConfig() else { guard let cfg = Self.loadConfig() else {
readiness = .error readiness = .error
return return
} }
self.config = cfg; self.config = cfg;
// Create our "runtime" config. The "runtime" is the configuration that ghostty // Create our "runtime" config. The "runtime" is the configuration that ghostty
// uses to interface with the application runtime environment. // uses to interface with the application runtime environment.
var runtime_cfg = ghostty_runtime_config_s( var runtime_cfg = ghostty_runtime_config_s(
@ -110,7 +119,7 @@ extension Ghostty {
return return
} }
self.app = app self.app = app
// Subscribe to notifications for keyboard layout change so that we can update Ghostty. // Subscribe to notifications for keyboard layout change so that we can update Ghostty.
NotificationCenter.default.addObserver( NotificationCenter.default.addObserver(
self, self,
@ -120,19 +129,19 @@ extension Ghostty {
self.readiness = .ready self.readiness = .ready
} }
deinit { deinit {
// This will force the didSet callbacks to run which free. // This will force the didSet callbacks to run which free.
self.app = nil self.app = nil
self.config = nil self.config = nil
// Remove our observer // Remove our observer
NotificationCenter.default.removeObserver( NotificationCenter.default.removeObserver(
self, self,
name: NSTextInputContext.keyboardSelectionDidChangeNotification, name: NSTextInputContext.keyboardSelectionDidChangeNotification,
object: nil) object: nil)
} }
/// Initializes a new configuration and loads all the values. /// Initializes a new configuration and loads all the values.
static func loadConfig() -> ghostty_config_t? { static func loadConfig() -> ghostty_config_t? {
// Initialize the global configuration. // Initialize the global configuration.
@ -140,19 +149,19 @@ extension Ghostty {
AppDelegate.logger.critical("ghostty_config_new failed") AppDelegate.logger.critical("ghostty_config_new failed")
return nil return nil
} }
// Load our configuration files from the home directory. // Load our configuration files from the home directory.
ghostty_config_load_default_files(cfg); ghostty_config_load_default_files(cfg);
ghostty_config_load_cli_args(cfg); ghostty_config_load_cli_args(cfg);
ghostty_config_load_recursive_files(cfg); ghostty_config_load_recursive_files(cfg);
// TODO: we'd probably do some config loading here... for now we'd // TODO: we'd probably do some config loading here... for now we'd
// have to do this synchronously. When we support config updating we can do // have to do this synchronously. When we support config updating we can do
// this async and update later. // this async and update later.
// Finalize will make our defaults available. // Finalize will make our defaults available.
ghostty_config_finalize(cfg) ghostty_config_finalize(cfg)
// Log any configuration errors. These will be automatically shown in a // Log any configuration errors. These will be automatically shown in a
// pop-up window too. // pop-up window too.
let errCount = ghostty_config_errors_count(cfg) let errCount = ghostty_config_errors_count(cfg)
@ -166,14 +175,14 @@ extension Ghostty {
AppDelegate.logger.warning("config error: \(message)") AppDelegate.logger.warning("config error: \(message)")
} }
} }
return cfg return cfg
} }
/// Returns the configuration errors (if any). /// Returns the configuration errors (if any).
func configErrors() -> [String] { func configErrors() -> [String] {
guard let cfg = self.config else { return [] } guard let cfg = self.config else { return [] }
var errors: [String] = []; var errors: [String] = [];
let errCount = ghostty_config_errors_count(cfg) let errCount = ghostty_config_errors_count(cfg)
for i in 0..<errCount { for i in 0..<errCount {
@ -181,70 +190,70 @@ extension Ghostty {
let message = String(cString: err.message) let message = String(cString: err.message)
errors.append(message) errors.append(message)
} }
return errors return errors
} }
func appTick() { func appTick() {
guard let app = self.app else { return } guard let app = self.app else { return }
// Tick our app, which lets us know if we want to quit // Tick our app, which lets us know if we want to quit
let exit = ghostty_app_tick(app) let exit = ghostty_app_tick(app)
if (!exit) { return } if (!exit) { return }
// We want to quit, start that process // We want to quit, start that process
NSApplication.shared.terminate(nil) NSApplication.shared.terminate(nil)
} }
func reloadConfig() { func reloadConfig() {
guard let app = self.app else { return } guard let app = self.app else { return }
ghostty_app_reload_config(app) ghostty_app_reload_config(app)
} }
/// Request that the given surface is closed. This will trigger the full normal surface close event /// Request that the given surface is closed. This will trigger the full normal surface close event
/// cycle which will call our close surface callback. /// cycle which will call our close surface callback.
func requestClose(surface: ghostty_surface_t) { func requestClose(surface: ghostty_surface_t) {
ghostty_surface_request_close(surface) ghostty_surface_request_close(surface)
} }
func newTab(surface: ghostty_surface_t) { func newTab(surface: ghostty_surface_t) {
let action = "new_tab" let action = "new_tab"
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
AppDelegate.logger.warning("action failed action=\(action)") AppDelegate.logger.warning("action failed action=\(action)")
} }
} }
func newWindow(surface: ghostty_surface_t) { func newWindow(surface: ghostty_surface_t) {
let action = "new_window" let action = "new_window"
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
AppDelegate.logger.warning("action failed action=\(action)") AppDelegate.logger.warning("action failed action=\(action)")
} }
} }
func split(surface: ghostty_surface_t, direction: ghostty_split_direction_e) { func split(surface: ghostty_surface_t, direction: ghostty_split_direction_e) {
ghostty_surface_split(surface, direction) ghostty_surface_split(surface, direction)
} }
func splitMoveFocus(surface: ghostty_surface_t, direction: SplitFocusDirection) { func splitMoveFocus(surface: ghostty_surface_t, direction: SplitFocusDirection) {
ghostty_surface_split_focus(surface, direction.toNative()) ghostty_surface_split_focus(surface, direction.toNative())
} }
func splitToggleZoom(surface: ghostty_surface_t) { func splitToggleZoom(surface: ghostty_surface_t) {
let action = "toggle_split_zoom" let action = "toggle_split_zoom"
if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) {
AppDelegate.logger.warning("action failed action=\(action)") AppDelegate.logger.warning("action failed action=\(action)")
} }
} }
// Called when the selected keyboard changes. We have to notify Ghostty so that // Called when the selected keyboard changes. We have to notify Ghostty so that
// it can reload the keyboard mapping for input. // it can reload the keyboard mapping for input.
@objc private func keyboardSelectionDidChange(notification: NSNotification) { @objc private func keyboardSelectionDidChange(notification: NSNotification) {
guard let app = self.app else { return } guard let app = self.app else { return }
ghostty_app_keyboard_changed(app) ghostty_app_keyboard_changed(app)
} }
// MARK: Ghostty Callbacks // MARK: Ghostty Callbacks
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) { static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e, config: ghostty_surface_config_s) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post(name: Notification.ghosttyNewSplit, object: surface, userInfo: [ NotificationCenter.default.post(name: Notification.ghosttyNewSplit, object: surface, userInfo: [
@ -252,14 +261,14 @@ extension Ghostty {
Notification.NewSurfaceConfigKey: config, Notification.NewSurfaceConfigKey: config,
]) ])
} }
static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) { static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [ NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface, userInfo: [
"process_alive": processAlive, "process_alive": processAlive,
]) ])
} }
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) { static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return } guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return }
@ -271,16 +280,16 @@ extension Ghostty {
] ]
) )
} }
static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) { static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post( NotificationCenter.default.post(
name: Notification.didToggleSplitZoom, name: Notification.didToggleSplitZoom,
object: surface object: surface
) )
} }
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) { static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post( NotificationCenter.default.post(
@ -291,59 +300,60 @@ extension Ghostty {
] ]
) )
} }
static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer<CChar>? { static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e) -> UnsafePointer<CChar>? {
// We only support the standard clipboard // We only support the standard clipboard
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil } if (location != GHOSTTY_CLIPBOARD_STANDARD) { return nil }
guard let appState = self.appState(fromSurface: userdata) else { return nil } guard let surface = self.surfaceUserdata(from: userdata) else { return nil }
guard let appState = self.appState(fromView: surface) else { return nil }
guard let str = NSPasteboard.general.string(forType: .string) else { return nil } guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
// Ghostty requires we cache the string because the pointer we return has to remain // Ghostty requires we cache the string because the pointer we return has to remain
// stable until the next call to readClipboard. // stable until the next call to readClipboard.
appState.cached_clipboard_string = str appState.cached_clipboard_string = str
return (str as NSString).utf8String return (str as NSString).utf8String
} }
static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) { static func writeClipboard(_ userdata: UnsafeMutableRawPointer?, string: UnsafePointer<CChar>?, location: ghostty_clipboard_e) {
// We only support the standard clipboard // We only support the standard clipboard
if (location != GHOSTTY_CLIPBOARD_STANDARD) { return } if (location != GHOSTTY_CLIPBOARD_STANDARD) { return }
guard let valueStr = String(cString: string!, encoding: .utf8) else { return } guard let valueStr = String(cString: string!, encoding: .utf8) else { return }
let pb = NSPasteboard.general let pb = NSPasteboard.general
pb.declareTypes([.string], owner: nil) pb.declareTypes([.string], owner: nil)
pb.setString(valueStr, forType: .string) pb.setString(valueStr, forType: .string)
} }
static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? { static func reloadConfig(_ userdata: UnsafeMutableRawPointer?) -> ghostty_config_t? {
guard let newConfig = Self.loadConfig() else { guard let newConfig = Self.loadConfig() else {
AppDelegate.logger.warning("failed to reload configuration") AppDelegate.logger.warning("failed to reload configuration")
return nil return nil
} }
// Assign the new config. This will automatically free the old config. // Assign the new config. This will automatically free the old config.
// It is safe to free the old config from within this function call. // It is safe to free the old config from within this function call.
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue() let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
state.config = newConfig state.config = newConfig
// If we have a delegate, notify. // If we have a delegate, notify.
if let delegate = state.delegate { if let delegate = state.delegate {
delegate.configDidReload(state) delegate.configDidReload(state)
} }
return newConfig return newConfig
} }
static func wakeup(_ userdata: UnsafeMutableRawPointer?) { static func wakeup(_ userdata: UnsafeMutableRawPointer?) {
let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue() let state = Unmanaged<AppState>.fromOpaque(userdata!).takeUnretainedValue()
// Wakeup can be called from any thread so we schedule the app tick // Wakeup can be called from any thread so we schedule the app tick
// from the main thread. There is probably some improvements we can make // from the main thread. There is probably some improvements we can make
// to coalesce multiple ticks but I don't think it matters from a performance // to coalesce multiple ticks but I don't think it matters from a performance
// standpoint since we don't do this much. // standpoint since we don't do this much.
DispatchQueue.main.async { state.appTick() } DispatchQueue.main.async { state.appTick() }
} }
static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) { static func setTitle(_ userdata: UnsafeMutableRawPointer?, title: UnsafePointer<CChar>?) {
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue() let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
guard let titleStr = String(cString: title!, encoding: .utf8) else { return } guard let titleStr = String(cString: title!, encoding: .utf8) else { return }
@ -351,12 +361,12 @@ extension Ghostty {
surfaceView.title = titleStr surfaceView.title = titleStr
} }
} }
static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) { static func setMouseShape(_ userdata: UnsafeMutableRawPointer?, shape: ghostty_mouse_shape_e) {
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue() let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
surfaceView.setCursorShape(shape) surfaceView.setCursorShape(shape)
} }
static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) { static func setMouseVisibility(_ userdata: UnsafeMutableRawPointer?, visible: Bool) {
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue() let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
surfaceView.setCursorVisibility(visible) surfaceView.setCursorVisibility(visible)
@ -372,10 +382,21 @@ extension Ghostty {
] ]
) )
} }
static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) { static func newTab(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
guard let appState = self.appState(fromView: surface) else { return }
guard appState.windowDecorations else {
let alert = NSAlert()
alert.messageText = "Tabs are disabled"
alert.informativeText = "Enable window decorations to use tabs"
alert.addButton(withTitle: "OK")
alert.alertStyle = .warning
_ = alert.runModal()
return
}
NotificationCenter.default.post( NotificationCenter.default.post(
name: Notification.ghosttyNewTab, name: Notification.ghosttyNewTab,
object: surface, object: surface,
@ -384,10 +405,10 @@ extension Ghostty {
] ]
) )
} }
static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) { static func newWindow(_ userdata: UnsafeMutableRawPointer?, config: ghostty_surface_config_s) {
guard let surface = self.surfaceUserdata(from: userdata) else { return } guard let surface = self.surfaceUserdata(from: userdata) else { return }
NotificationCenter.default.post( NotificationCenter.default.post(
name: Notification.ghosttyNewWindow, name: Notification.ghosttyNewWindow,
object: surface, object: surface,
@ -396,16 +417,15 @@ extension Ghostty {
] ]
) )
} }
/// Returns the GhosttyState from the given userdata value. /// Returns the GhosttyState from the given userdata value.
static private func appState(fromSurface userdata: UnsafeMutableRawPointer?) -> AppState? { static private func appState(fromView view: SurfaceView) -> AppState? {
let surfaceView = Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue() guard let surface = view.surface else { return nil }
guard let surface = surfaceView.surface else { return nil }
guard let app = ghostty_surface_app(surface) else { return nil } guard let app = ghostty_surface_app(surface) else { return nil }
guard let app_ud = ghostty_app_userdata(app) else { return nil } guard let app_ud = ghostty_app_userdata(app) else { return nil }
return Unmanaged<AppState>.fromOpaque(app_ud).takeUnretainedValue() return Unmanaged<AppState>.fromOpaque(app_ud).takeUnretainedValue()
} }
/// Returns the surface view from the userdata. /// Returns the surface view from the userdata.
static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView? { static private func surfaceUserdata(from userdata: UnsafeMutableRawPointer?) -> SurfaceView? {
return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue() return Unmanaged<SurfaceView>.fromOpaque(userdata!).takeUnretainedValue()
@ -428,7 +448,7 @@ extension EnvironmentValues {
get { self[GhosttyAppKey.self] } get { self[GhosttyAppKey.self] }
set { self[GhosttyAppKey.self] = newValue } set { self[GhosttyAppKey.self] = newValue }
} }
var ghosttyConfig: ghostty_config_t? { var ghosttyConfig: ghostty_config_t? {
get { self[GhosttyConfigKey.self] } get { self[GhosttyConfigKey.self] }
set { self[GhosttyConfigKey.self] = newValue } set { self[GhosttyConfigKey.self] = newValue }
@ -439,7 +459,7 @@ extension View {
func ghosttyApp(_ app: ghostty_app_t?) -> some View { func ghosttyApp(_ app: ghostty_app_t?) -> some View {
environment(\.ghosttyApp, app) environment(\.ghosttyApp, app)
} }
func ghosttyConfig(_ config: ghostty_config_t?) -> some View { func ghosttyConfig(_ config: ghostty_config_t?) -> some View {
environment(\.ghosttyConfig, config) environment(\.ghosttyConfig, config)
} }

View File

@ -1,16 +1,16 @@
import SwiftUI import SwiftUI
import GhosttyKit import GhosttyKit
class FullScreenHandler { class FullScreenHandler { var previousTabGroup: NSWindowTabGroup?
var previousTabGroup: NSWindowTabGroup?
var previousTabGroupIndex: Int? var previousTabGroupIndex: Int?
var previousContentFrame: NSRect? var previousContentFrame: NSRect?
var isInFullscreen: Bool = false var previousStyleMask: NSWindow.StyleMask? = nil
// We keep track of whether we entered non-native fullscreen in case // We keep track of whether we entered non-native fullscreen in case
// a user goes to fullscreen, changes the config to disable non-native fullscreen // a user goes to fullscreen, changes the config to disable non-native fullscreen
// and then wants to toggle it off // and then wants to toggle it off
var isInNonNativeFullscreen: Bool = false var isInNonNativeFullscreen: Bool = false
var isInFullscreen: Bool = false
func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) { func toggleFullscreen(window: NSWindow, nonNativeFullscreen: ghostty_non_native_fullscreen_e) {
let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE let useNonNativeFullscreen = nonNativeFullscreen != GHOSTTY_NON_NATIVE_FULLSCREEN_FALSE
@ -89,6 +89,7 @@ class FullScreenHandler {
// This is important: it gives us the full screen, including the // This is important: it gives us the full screen, including the
// notch area on MacBooks. // notch area on MacBooks.
self.previousStyleMask = window.styleMask
window.styleMask.remove(.titled) window.styleMask.remove(.titled)
// Set frame to screen size, accounting for the menu bar if needed // Set frame to screen size, accounting for the menu bar if needed
@ -126,8 +127,8 @@ class FullScreenHandler {
func leaveFullscreen(window: NSWindow) { func leaveFullscreen(window: NSWindow) {
guard let previousFrame = previousContentFrame else { return } guard let previousFrame = previousContentFrame else { return }
// Restore title bar // Restore the style mask
window.styleMask.insert(.titled) window.styleMask = self.previousStyleMask!
// Restore previous presentation options // Restore previous presentation options
NSApp.presentationOptions = [] NSApp.presentationOptions = []

View File

@ -242,7 +242,6 @@ keybind: Keybinds = .{},
/// If false, windows won't have native decorations, i.e. titlebar and /// If false, windows won't have native decorations, i.e. titlebar and
/// borders. /// borders.
/// Currently only supported with GTK.
@"window-decoration": bool = true, @"window-decoration": bool = true,
/// Whether to allow programs running in the terminal to read/write to /// Whether to allow programs running in the terminal to read/write to