diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index b0c2df1ca..601d76956 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -71,8 +71,8 @@ class TerminalController: NSWindowController, NSWindowDelegate, super.init(window: nil) // Initialize our initial surface. - guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") } - self.surfaceTree = tree ?? .leaf(.init(ghostty_app, baseConfig: base)) + guard ghostty.app != nil else { preconditionFailure("app must be loaded") } + self.surfaceTree = tree ?? .leaf(.init(ghostty, baseConfig: base)) // Setup our notifications for behaviors let center = NotificationCenter.default diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index 2f455e578..c3037d5e3 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -236,6 +236,14 @@ extension Ghostty { return v } + var macosWindowShadow: Bool { + guard let config = self.config else { return false } + var v = false; + let key = "macos-window-shadow" + _ = ghostty_config_get(config, &v, key, UInt(key.count)) + return v + } + var backgroundColor: Color { var rgb: UInt32 = 0 let bg_key = "background" diff --git a/macos/Sources/Ghostty/Ghostty.SplitNode.swift b/macos/Sources/Ghostty/Ghostty.SplitNode.swift index 5dc1ec492..90a9bd6f9 100644 --- a/macos/Sources/Ghostty/Ghostty.SplitNode.swift +++ b/macos/Sources/Ghostty/Ghostty.SplitNode.swift @@ -168,13 +168,13 @@ extension Ghostty { } class Leaf: ObservableObject, Equatable, Hashable, Codable { - let app: ghostty_app_t + let app: Ghostty.App @Published var surface: SurfaceView weak var parent: SplitNode.Container? /// Initialize a new leaf which creates a new terminal surface. - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { + init(_ app: Ghostty.App, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { self.app = app self.surface = SurfaceView(app, baseConfig: baseConfig, uuid: uuid) } @@ -182,14 +182,14 @@ extension Ghostty { // MARK: - Hashable func hash(into hasher: inout Hasher) { - hasher.combine(app) + hasher.combine(app.app) hasher.combine(surface) } // MARK: - Equatable static func == (lhs: Leaf, rhs: Leaf) -> Bool { - return lhs.app == rhs.app && lhs.surface === rhs.surface + return lhs.app.app == rhs.app.app && lhs.surface === rhs.surface } // MARK: - Codable @@ -203,7 +203,7 @@ extension Ghostty { // Decoding uses the global Ghostty app guard let del = NSApplication.shared.delegate, let appDel = del as? AppDelegate, - let app = appDel.ghostty.app else { + appDel.ghostty.app != nil else { throw TerminalRestoreError.delegateInvalid } @@ -212,7 +212,7 @@ extension Ghostty { var config = SurfaceConfiguration() config.workingDirectory = try container.decode(String?.self, forKey: .pwd) - self.init(app, baseConfig: config, uuid: uuid) + self.init(appDel.ghostty, baseConfig: config, uuid: uuid) } func encode(to encoder: Encoder) throws { @@ -223,7 +223,7 @@ extension Ghostty { } class Container: ObservableObject, Equatable, Hashable, Codable { - let app: ghostty_app_t + let app: Ghostty.App let direction: SplitViewDirection @Published var topLeft: SplitNode @@ -335,7 +335,7 @@ extension Ghostty { // MARK: - Hashable func hash(into hasher: inout Hasher) { - hasher.combine(app) + hasher.combine(app.app) hasher.combine(direction) hasher.combine(topLeft) hasher.combine(bottomRight) @@ -344,7 +344,7 @@ extension Ghostty { // MARK: - Equatable static func == (lhs: Container, rhs: Container) -> Bool { - return lhs.app == rhs.app && + return lhs.app.app == rhs.app.app && lhs.direction == rhs.direction && lhs.topLeft == rhs.topLeft && lhs.bottomRight == rhs.bottomRight @@ -363,12 +363,12 @@ extension Ghostty { // Decoding uses the global Ghostty app guard let del = NSApplication.shared.delegate, let appDel = del as? AppDelegate, - let app = appDel.ghostty.app else { + appDel.ghostty.app != nil else { throw TerminalRestoreError.delegateInvalid } let container = try decoder.container(keyedBy: CodingKeys.self) - self.app = app + self.app = appDel.ghostty self.direction = try container.decode(SplitViewDirection.self, forKey: .direction) self.split = try container.decode(CGFloat.self, forKey: .split) self.topLeft = try container.decode(SplitNode.self, forKey: .topLeft) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 4d755e70e..1a5efc5c6 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -9,8 +9,8 @@ extension Ghostty { @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle: String? var body: some View { - if let app = self.ghostty.app { - SurfaceForApp(app) { surfaceView in + if self.ghostty.app != nil { + SurfaceForApp(self.ghostty) { surfaceView in SurfaceWrapper(surfaceView: surfaceView) } .navigationTitle(surfaceTitle ?? "Ghostty") @@ -24,7 +24,7 @@ extension Ghostty { @StateObject private var surfaceView: SurfaceView - init(_ app: ghostty_app_t, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) { + init(_ app: Ghostty.App, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) { _surfaceView = StateObject(wrappedValue: SurfaceView(app)) self.content = content } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 255248c61..fa7ac717d 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -85,6 +85,8 @@ extension Ghostty { // I don't think we need this but this lets us know we should redraw our layer // so we'll use that to tell ghostty to refresh. override var wantsUpdateLayer: Bool { return true } + + private let app: Ghostty.App // State machine for mouse cursor visibility because every call to // NSCursor.hide/unhide must be balanced. @@ -95,9 +97,10 @@ extension Ghostty { case pendingHidden } - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { + init(_ app: Ghostty.App, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { self.markedText = NSMutableAttributedString() self.uuid = uuid ?? .init() + self.app = app // Initialize with some default frame size. The important thing is that this // is non-zero so that our layer bounds are non-zero so that our renderer @@ -121,7 +124,7 @@ extension Ghostty { // Setup our surface. This will also initialize all the terminal IO. let surface_cfg = baseConfig ?? SurfaceConfiguration() var surface_cfg_c = surface_cfg.ghosttyConfig(view: self) - guard let surface = ghostty_surface_new(app, &surface_cfg_c) else { + guard let surface = ghostty_surface_new(app.app, &surface_cfg_c) else { self.error = AppError.surfaceCreateError return } @@ -369,8 +372,16 @@ extension Ghostty { // Set the window transparency settings window.isOpaque = false - window.hasShadow = false - window.backgroundColor = .clear + + if app.config.macosWindowShadow { + window.hasShadow = true + // NOTE(haze): I have no idea why this works, but it does. This emulates the + // aesthetic of `Terminal.app`. + window.backgroundColor = .white.withAlphaComponent(0.001) + } else { + window.hasShadow = false + window.backgroundColor = .clear + } // If we have a blur, set the blur ghostty_set_window_background_blur(surface, Unmanaged.passUnretained(window).toOpaque()) diff --git a/src/config/Config.zig b/src/config/Config.zig index cf38788cb..e2e67da0a 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -976,6 +976,9 @@ keybind: Keybinds = .{}, /// This option only applies to new windows when changed. @"macos-titlebar-tabs": bool = false, +/// If `true`, render the drop shadow behind the macOS window. +@"macos-window-shadow": bool = true, + /// If `true`, the *Option* key will be treated as *Alt*. This makes terminal /// sequences expecting *Alt* to work properly, but will break Unicode input /// sequences on macOS if you use them via the *Alt* key. You may set this to