diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 4900d6c49..96cbaca0a 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -102,7 +102,6 @@ "Features/Custom App Icon/Extensions/UserDefaults+AppIcon.swift", Ghostty/Ghostty.ConfigTypes.swift, Ghostty/GhosttyPackageMeta.swift, - Helpers/CrossKit.swift, "Helpers/Extensions/NSImage+Extension.swift", "Helpers/Extensions/OSColor+Extension.swift", "Helpers/Extensions/OSPasteboard+Extension.swift", @@ -113,7 +112,6 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( "Features/Custom App Icon/DockTilePlugin.swift", - "Ghostty/Surface View/SurfaceView_UIKit.swift", ); target = A5B30530299BEAAA0047F10C /* Ghostty */; }; diff --git a/macos/Sources/Features/About/AboutView.swift b/macos/Sources/Features/About/AboutView.swift index af6f64504..114379e2a 100644 --- a/macos/Sources/Features/About/AboutView.swift +++ b/macos/Sources/Features/About/AboutView.swift @@ -45,7 +45,6 @@ struct AboutView: View { private var copyright: String? { Bundle.main.infoDictionary?["NSHumanReadableCopyright"] as? String } - #if os(macOS) // This creates a background style similar to the Apple "About My Mac" Window private struct VisualEffectBackground: NSViewRepresentable { let material: NSVisualEffectView.Material @@ -72,7 +71,6 @@ struct AboutView: View { return visualEffect } } - #endif var body: some View { VStack(alignment: .center) { @@ -141,9 +139,7 @@ struct AboutView: View { .padding(.top, 8) .padding(32) .frame(minWidth: 256) - #if os(macOS) .background(VisualEffectBackground(material: .underWindowBackground).ignoresSafeArea()) - #endif } private struct PropertyRow: View { diff --git a/macos/Sources/Features/Command Palette/CommandPalette.swift b/macos/Sources/Features/Command Palette/CommandPalette.swift index 20ffb5c2b..5a1b5cb36 100644 --- a/macos/Sources/Features/Command Palette/CommandPalette.swift +++ b/macos/Sources/Features/Command Palette/CommandPalette.swift @@ -90,7 +90,7 @@ struct CommandPaletteView: View { } var body: some View { - let scheme: ColorScheme = if OSColor(backgroundColor).isLightColor { + let scheme: ColorScheme = if NSColor(backgroundColor).isLightColor { .light } else { .dark diff --git a/macos/Sources/Features/Custom App Icon/ColorizedGhosttyIconImage.swift b/macos/Sources/Features/Custom App Icon/ColorizedGhosttyIconImage.swift index d7bae0439..980da6b79 100644 --- a/macos/Sources/Features/Custom App Icon/ColorizedGhosttyIconImage.swift +++ b/macos/Sources/Features/Custom App Icon/ColorizedGhosttyIconImage.swift @@ -3,7 +3,6 @@ import SwiftUI extension View { /// Returns the ghostty icon to use for views. func ghosttyIconImage() -> Image { - #if os(macOS) // Grab the icon from the running application. This is the best way // I've found so far to get the proper icon for our current icon // tinting and so on with macOS Tahoe @@ -15,7 +14,6 @@ extension View { if let nsImage = NSApp.applicationIconImage { return Image(nsImage: nsImage) } - #endif // Fall back to a static representation return Image("AppIconImage") diff --git a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift index 905e88229..bb59d9a93 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift @@ -147,8 +147,8 @@ class TitlebarTabsVenturaTerminalWindow: TerminalWindow { } // Update our window light/darkness based on our updated background color - let themeChanged = isLightTheme != OSColor(surfaceConfig.backgroundColor).isLightColor - isLightTheme = OSColor(surfaceConfig.backgroundColor).isLightColor + let themeChanged = isLightTheme != NSColor(surfaceConfig.backgroundColor).isLightColor + isLightTheme = NSColor(surfaceConfig.backgroundColor).isLightColor // Update our titlebar color if let preferredBackgroundColor { diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 2bb3e2f28..495dd8232 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -2,22 +2,15 @@ import SwiftUI import UniformTypeIdentifiers import UserNotifications import GhosttyKit - -#if os(macOS) import AppKit -#endif protocol GhosttyAppDelegate: AnyObject { - #if os(macOS) /// Called when a callback needs access to a specific surface. This should return nil /// when the surface is no longer valid. func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView? - #endif } extension Ghostty { - // IMPORTANT: THIS IS NOT DONE. - // This is a refactor/redo of Ghostty.AppState so that it supports both macOS and iOS class App: ObservableObject { enum Readiness: String { case loading, error, ready @@ -81,8 +74,6 @@ extension Ghostty { return } self.app = app - -#if os(macOS) // Set our initial focus state ghostty_app_set_focus(app, NSApp.isActive) @@ -102,18 +93,13 @@ extension Ghostty { selector: #selector(applicationDidResignActive(notification:)), name: NSApplication.didResignActiveNotification, object: nil) -#endif - self.readiness = .ready } deinit { // This will force the didSet callbacks to run which free. self.app = nil - -#if os(macOS) NotificationCenter.default.removeObserver(self) -#endif } // MARK: App Operations @@ -132,7 +118,6 @@ extension Ghostty { func openConfig() { let str = configPath ?? Ghostty.AllocatedString(ghostty_config_open_path()).string guard !str.isEmpty else { return } - #if os(macOS) let fileURL = URL(fileURLWithPath: str).absoluteString var action = ghostty_action_open_url_s() action.kind = GHOSTTY_ACTION_OPEN_URL_KIND_TEXT @@ -141,9 +126,6 @@ extension Ghostty { action.len = UInt(fileURL.count) _ = App.openURL(action) } - #else - fatalError("Unsupported platform for opening config file") - #endif } /// Reload the configuration. @@ -271,39 +253,6 @@ extension Ghostty { } } - #if os(iOS) - // MARK: Ghostty Callbacks (iOS) - - static func wakeup(_ userdata: UnsafeMutableRawPointer?) {} - static func action(_ app: ghostty_app_t, target: ghostty_target_s, action: ghostty_action_s) -> Bool { return false } - static func readClipboard( - _ userdata: UnsafeMutableRawPointer?, - location: ghostty_clipboard_e, - state: UnsafeMutableRawPointer? - ) -> Bool { - return false - } - - static func confirmReadClipboard( - _ userdata: UnsafeMutableRawPointer?, - string: UnsafePointer?, - state: UnsafeMutableRawPointer?, - request: ghostty_clipboard_request_e - ) {} - - static func writeClipboard( - _ userdata: UnsafeMutableRawPointer?, - location: ghostty_clipboard_e, - content: UnsafePointer?, - len: Int, - confirm: Bool - ) {} - - static func closeSurface(_ userdata: UnsafeMutableRawPointer?, processAlive: Bool) {} - #endif - - #if os(macOS) - // MARK: Notifications // Called when the selected keyboard changes. We have to notify Ghostty so that @@ -700,18 +649,8 @@ extension Ghostty { } private static func quit(_ app: ghostty_app_t) { - // On iOS, applications do not terminate programmatically like they do - // on macOS. On iOS, applications are only terminated when a user physically - // closes the application (i.e. going to the home screen). If we request - // exit on iOS we ignore it. - #if os(iOS) - logger.info("quit request received, ignoring on iOS") - #endif - - #if os(macOS) // We want to quit, start that process NSApplication.shared.terminate(nil) - #endif } private static func checkForUpdates( @@ -2369,7 +2308,5 @@ extension Ghostty { break } } - - #endif } } diff --git a/macos/Sources/Ghostty/Ghostty.Config.swift b/macos/Sources/Ghostty/Ghostty.Config.swift index cb4b4b415..bfe42b114 100644 --- a/macos/Sources/Ghostty/Ghostty.Config.swift +++ b/macos/Sources/Ghostty/Ghostty.Config.swift @@ -65,9 +65,6 @@ extension Ghostty { } // Load our configuration from files, CLI args, and then any referenced files. - // We only do this on macOS because other Apple platforms do not have the - // same filesystem concept. -#if os(macOS) if let path { ghostty_config_load_file(cfg, path) } else { @@ -81,7 +78,6 @@ extension Ghostty { } ghostty_config_load_recursive_files(cfg) -#endif // 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 @@ -108,7 +104,6 @@ extension Ghostty { return cfg } -#if os(macOS) // MARK: - Keybindings /// Return the key equivalent for the given action. The action is the name of the action @@ -122,7 +117,6 @@ extension Ghostty { let trigger = ghostty_config_trigger(cfg, action, UInt(action.lengthOfBytes(using: .utf8))) return Ghostty.keyboardShortcut(for: trigger) } -#endif // MARK: - Configuration Values @@ -287,7 +281,6 @@ extension Ghostty { /// 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? @@ -310,21 +303,9 @@ extension Ghostty { nil } } - #else - var windowFullscreen: Bool { - guard let config = self.config else { return false } - var v: UnsafePointer? - 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 guard let config = self.config else { return defaultValue } @@ -346,7 +327,6 @@ extension Ghostty { defaultValue } } - #endif var windowTitleFontFamily: String? { guard let config = self.config else { return nil } @@ -420,7 +400,6 @@ extension Ghostty { } var macosCustomIcon: String { - #if os(macOS) let defaultValue = NSString("~/.config/ghostty/Ghostty.icns").expandingTildeInPath guard let config = self.config else { return defaultValue } var v: UnsafePointer? @@ -429,9 +408,6 @@ extension Ghostty { guard let ptr = v else { return defaultValue } guard let path = NSString(utf8String: ptr) else { return defaultValue } return path.expandingTildeInPath - #else - return "" - #endif } var macosIconFrame: MacOSIconFrame { @@ -445,7 +421,7 @@ extension Ghostty { return MacOSIconFrame(rawValue: str) ?? defaultValue } - var macosIconGhostColor: OSColor? { + var macosIconGhostColor: NSColor? { guard let config = self.config else { return nil } var v: ghostty_config_color_s = .init() let key = "macos-icon-ghost-color" @@ -453,7 +429,7 @@ extension Ghostty { return .init(ghostty: v) } - var macosIconScreenColor: [OSColor]? { + var macosIconScreenColor: [NSColor]? { guard let config = self.config else { return nil } var v: ghostty_config_color_list_s = .init() let key = "macos-icon-screen-color" @@ -485,13 +461,7 @@ extension Ghostty { var color: ghostty_config_color_s = .init() let bg_key = "background" if !ghostty_config_get(config, &color, bg_key, UInt(bg_key.lengthOfBytes(using: .utf8))) { -#if os(macOS) return Color(NSColor.windowBackgroundColor) -#elseif os(iOS) - return Color(UIColor.systemBackground) -#else -#error("unsupported") -#endif } return .init( @@ -543,7 +513,7 @@ extension Ghostty { } var splitDividerColor: Color { - let backgroundColor = OSColor(backgroundColor) + let backgroundColor = NSColor(backgroundColor) let isLightBackground = backgroundColor.isLightColor let newColor = isLightBackground ? backgroundColor.darken(by: 0.08) : backgroundColor.darken(by: 0.4) @@ -562,7 +532,6 @@ extension Ghostty { ) } - #if canImport(AppKit) var quickTerminalPosition: QuickTerminalPosition { guard let config = self.config else { return .top } var v: UnsafePointer? @@ -616,7 +585,6 @@ extension Ghostty { guard ghostty_config_get(config, &v, key, UInt(key.lengthOfBytes(using: .utf8))) else { return QuickTerminalSize() } return QuickTerminalSize(from: v) } - #endif var resizeOverlay: ResizeOverlay { guard let config = self.config else { return .after_first } diff --git a/macos/Sources/Ghostty/Ghostty.Input.swift b/macos/Sources/Ghostty/Ghostty.Input.swift index d90fb987e..308312bce 100644 --- a/macos/Sources/Ghostty/Ghostty.Input.swift +++ b/macos/Sources/Ghostty/Ghostty.Input.swift @@ -518,7 +518,6 @@ extension Ghostty.Input.Momentum: AppEnum { ] } -#if canImport(AppKit) import AppKit extension Ghostty.Input.Momentum { @@ -535,7 +534,6 @@ extension Ghostty.Input.Momentum { } } } -#endif // MARK: Ghostty.Input.Mods diff --git a/macos/Sources/Ghostty/GhosttyPackage.swift b/macos/Sources/Ghostty/GhosttyPackage.swift index 071de3c8f..68c196dbc 100644 --- a/macos/Sources/Ghostty/GhosttyPackage.swift +++ b/macos/Sources/Ghostty/GhosttyPackage.swift @@ -216,7 +216,6 @@ extension Ghostty { } } -#if canImport(AppKit) // MARK: SplitFocusDirection Extensions extension Ghostty.SplitFocusDirection { @@ -243,7 +242,6 @@ extension Ghostty.SplitFocusDirection { } } } -#endif extension Ghostty { struct ClipboardContent { diff --git a/macos/Sources/Ghostty/Surface View/OSSurfaceView.swift b/macos/Sources/Ghostty/Surface View/OSSurfaceView.swift index 5176a958b..a85f9d626 100644 --- a/macos/Sources/Ghostty/Surface View/OSSurfaceView.swift +++ b/macos/Sources/Ghostty/Surface View/OSSurfaceView.swift @@ -3,7 +3,7 @@ import GhosttyKit import SwiftUI extension Ghostty { - class OSSurfaceView: OSView, ObservableObject { + class OSSurfaceView: NSView, ObservableObject { typealias ID = UUID /// Unique ID per surface @@ -129,7 +129,7 @@ extension Ghostty.OSSurfaceView { /// The pasteboard used to persist the search needle. /// /// The `.find` pasteboard lets us sync our needle across the system and other find bars. - private let pasteboard: OSPasteboard + private let pasteboard: NSPasteboard @Published var needle = Needle.empty @@ -138,7 +138,7 @@ extension Ghostty.OSSurfaceView { init( from startSearch: Ghostty.Action.StartSearch, - pasteboard: OSPasteboard = OSPasteboard.find + pasteboard: NSPasteboard = NSPasteboard.find ) { self.pasteboard = pasteboard if let needle = startSearch.needle, !needle.isEmpty { @@ -173,9 +173,7 @@ extension Ghostty.OSSurfaceView { guard let surface = self.surface else { return false } let action = "navigate_search:next" if !ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8))) { -#if canImport(AppKit) AppDelegate.logger.warning("action failed action=\(action, privacy: .public)") -#endif return false } return true @@ -185,9 +183,7 @@ extension Ghostty.OSSurfaceView { guard let surface = self.surface else { return false } let action = "navigate_search:previous" if !ghostty_surface_binding_action(surface, action, UInt(action.lengthOfBytes(using: .utf8))) { -#if canImport(AppKit) AppDelegate.logger.warning("action failed action=\(action, privacy: .public)") -#endif return false } return true diff --git a/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift b/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift index aab99c088..eccd0d701 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceScrollView.swift @@ -186,7 +186,7 @@ class SurfaceScrollView: NSView { private func synchronizeAppearance() { let scrollbarConfig = surfaceView.derivedConfig.scrollbar scrollView.hasVerticalScroller = scrollbarConfig != .never - let hasLightBackground = OSColor(surfaceView.derivedConfig.backgroundColor).isLightColor + let hasLightBackground = NSColor(surfaceView.derivedConfig.backgroundColor).isLightColor // Make sure the scroller’s appearance matches the surface's background color. scrollView.appearance = NSAppearance(named: hasLightBackground ? .aqua : .darkAqua) updateTrackingAreas() diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift b/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift index 11b7b4694..20913137d 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView+Image.swift @@ -1,11 +1,6 @@ -#if canImport(AppKit) import AppKit -#elseif canImport(UIKit) -import UIKit -#endif extension Ghostty.SurfaceView { - #if canImport(AppKit) /// A snapshot image of the current surface view. var asImage: NSImage? { guard let bitmapRep = bitmapImageRepForCachingDisplay(in: bounds) else { @@ -16,13 +11,4 @@ extension Ghostty.SurfaceView { image.addRepresentation(bitmapRep) return image } - #elseif canImport(UIKit) - /// A snapshot image of the current surface view. - var asImage: UIImage? { - let renderer = UIGraphicsImageRenderer(bounds: bounds) - return renderer.image { _ in - drawHierarchy(in: bounds, afterScreenUpdates: true) - } - } - #endif } diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift b/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift index 106875813..b8f3aac68 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView+Transferable.swift @@ -1,6 +1,4 @@ -#if canImport(AppKit) import AppKit -#endif import CoreTransferable import UniformTypeIdentifiers @@ -32,15 +30,8 @@ extension Ghostty.SurfaceView: Transferable { @MainActor static func find(uuid: UUID) -> Self? { - #if canImport(AppKit) guard let del = NSApp.delegate as? Ghostty.Delegate else { return nil } return del.ghosttySurface(id: uuid) as? Self - #elseif canImport(UIKit) - // We should be able to use UIApplication here. - return nil - #else - return nil - #endif } } @@ -50,9 +41,7 @@ extension UTType { static let ghosttySurfaceId = UTType(exportedAs: "com.mitchellh.ghosttySurfaceId") } -#if canImport(AppKit) extension NSPasteboard.PasteboardType { /// Pasteboard type for dragging surface IDs. static let ghosttySurfaceId = NSPasteboard.PasteboardType(UTType.ghosttySurfaceId.identifier) } -#endif diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView.swift b/macos/Sources/Ghostty/Surface View/SurfaceView.swift index 3e3deb398..e1fab0a3a 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView.swift @@ -19,10 +19,8 @@ extension Ghostty { // Maintain whether our window has focus (is key) or not @State private var windowFocus: Bool = true - #if canImport(AppKit) // Observe SecureInput to detect when its enabled @ObservedObject private var secureInput = SecureInput.shared - #endif @EnvironmentObject private var ghostty: Ghostty.App @Environment(\.ghosttyLastFocusedSurface) private var lastFocusedSurface @@ -39,17 +37,14 @@ extension Ghostty { // is up to date. See TerminalSurfaceView for why we don't use the NSView // resize callback. GeometryReader { geo in - #if canImport(AppKit) let pubBecomeKey = center.publisher(for: NSWindow.didBecomeKeyNotification) let pubResign = center.publisher(for: NSWindow.didResignKeyNotification) - #endif SurfaceRepresentable(view: surfaceView, size: geo.size) .focused($surfaceFocus) .focusedValue(\.ghosttySurfacePwd, surfaceView.pwd) .focusedValue(\.ghosttySurfaceView, surfaceView) .focusedValue(\.ghosttySurfaceCellSize, surfaceView.cellSize) - #if canImport(AppKit) .onReceive(pubBecomeKey) { notification in guard let window = notification.object as? NSWindow else { return } guard let surfaceWindow = surfaceView.window else { return } @@ -62,7 +57,6 @@ extension Ghostty { windowFocus = false } } - #endif // If our geo size changed then we show the resize overlay as configured. if let surfaceSize = surfaceView.surfaceSize { @@ -89,7 +83,6 @@ extension Ghostty { .transition(.opacity) } -#if canImport(AppKit) // Readonly indicator badge if surfaceView.readonly { ReadonlyBadge { @@ -103,7 +96,6 @@ extension Ghostty { keySequence: surfaceView.keySequence ) .zIndex(1) -#endif VStack(spacing: 0) { // If we have a URL from hovering a link, we show that. @@ -119,7 +111,6 @@ extension Ghostty { } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom) - #if canImport(AppKit) // If we have secure input enabled and we're the focused surface and window // then we want to show the secure input overlay. if ghostty.config.secureInputIndication && @@ -128,7 +119,6 @@ extension Ghostty { windowFocus { SecureInputOverlay() } - #endif // Search overlay if let searchState = surfaceView.searchState { @@ -171,16 +161,12 @@ extension Ghostty { } } - #if canImport(AppKit) // Grab handle for dragging the window. We want this to appear at the very // top Z-index os it isn't faded by the unfocused overlay. - // - // This is disabled except on macOS because it uses AppKit drag/drop APIs. SurfaceGrabHandle( surfaceView: surfaceView, dragHandle: ghostty.config.dragHandle, ) - #endif } } } @@ -384,7 +370,7 @@ extension Ghostty { } .onReceive( NotificationCenter.default.publisher( - for: OSApplication.didBecomeActiveNotification + for: NSApplication.didBecomeActiveNotification ) ) { _ in // When the app becomes active, we want to check for external changes @@ -394,7 +380,6 @@ extension Ghostty { .onSubmit { _ = surfaceView.navigateSearchToNext() } -#if canImport(AppKit) .onExitCommand { if searchState.needle.text.isEmpty { onClose() @@ -402,7 +387,6 @@ extension Ghostty { Ghostty.moveFocus(to: surfaceView) } } -#endif .backport.onKeyPress(.return) { modifiers in if modifiers.contains(.shift) { _ = surfaceView.navigateSearchToPrevious() @@ -477,7 +461,7 @@ extension Ghostty { } private var clipShape: some Shape { - if #available(iOS 26.0, macOS 26.0, *) { + if #available(macOS 26.0, *) { return ConcentricRectangle(corners: .concentric(minimum: 8), isUniform: true) } else { return RoundedRectangle(cornerRadius: 8) @@ -557,7 +541,7 @@ extension Ghostty { /// A surface is terminology in Ghostty for a terminal surface, or a place where a terminal is actually drawn /// and interacted with. The word "surface" is used because a surface may represent a window, a tab, /// a split, a small preview pane, etc. It is ANYTHING that has a terminal drawn to it. - struct SurfaceRepresentable: OSViewRepresentable { + struct SurfaceRepresentable: NSViewRepresentable { /// The view to render for the terminal surface. let view: SurfaceView @@ -571,13 +555,11 @@ extension Ghostty { /// The best approach is to wrap this view in a GeometryReader and pass in the geo.size. let size: CGSize - #if canImport(AppKit) - func makeOSView(context: Context) -> SurfaceScrollView { - // On macOS, wrap the surface view in a scroll view + func makeNSView(context: Context) -> SurfaceScrollView { return SurfaceScrollView(contentSize: size, surfaceView: view) } - func updateOSView(_ scrollView: SurfaceScrollView, context: Context) { + func updateNSView(_ scrollView: SurfaceScrollView, context: Context) { // SwiftUI may defer frame updates under system load (e.g., memory // pressure, heavy I/O) or when external window managers trigger rapid // layout changes. When that happens, the scroll view's bounds can @@ -586,16 +568,6 @@ extension Ghostty { guard scrollView.bounds.size != size else { return } scrollView.needsLayout = true } - #else - func makeOSView(context: Context) -> SurfaceView { - // On iOS, return the surface view directly - return view - } - - func updateOSView(_ view: SurfaceView, context: Context) { - view.sizeDidChange(size) - } - #endif } /// The configuration for a surface. For any configuration not set, defaults will be chosen from @@ -656,25 +628,11 @@ extension Ghostty { func withCValue(view: SurfaceView, _ body: (inout ghostty_surface_config_s) throws -> T) rethrows -> T { var config = ghostty_surface_config_new() config.userdata = Unmanaged.passUnretained(view).toOpaque() -#if os(macOS) config.platform_tag = GHOSTTY_PLATFORM_MACOS config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s( nsview: Unmanaged.passUnretained(view).toOpaque() )) config.scale_factor = NSScreen.main!.backingScaleFactor -#elseif os(iOS) - config.platform_tag = GHOSTTY_PLATFORM_IOS - config.platform = ghostty_platform_u(ios: ghostty_platform_ios_s( - uiview: Unmanaged.passUnretained(view).toOpaque() - )) - // Note that UIScreen.main is deprecated and we're supposed to get the - // screen through the view hierarchy instead. This means that we should - // probably set this to some default, then modify the scale factor through - // libghostty APIs when a UIView is attached to a window/scene. TODO. - config.scale_factor = UIScreen.main.scale -#else -#error("unsupported target") -#endif // Zero is our default value that means to inherit the font size. config.font_size = fontSize ?? 0 @@ -725,7 +683,6 @@ extension Ghostty { } } -#if canImport(AppKit) /// Floating indicator that shows active key tables and pending key sequences. /// Displayed as a compact draggable pill that can be positioned at the top or bottom. struct KeyStateIndicator: View { @@ -943,7 +900,6 @@ extension Ghostty { } } } -#endif /// Visual overlay that shows a border around the edges when the bell rings with border feature enabled. struct BellBorderOverlay: View { @@ -1107,7 +1063,6 @@ extension Ghostty { } } - #if canImport(AppKit) /// When changing the split state, or going full screen (native or non), the terminal view /// will lose focus. There has to be some nice SwiftUI-native way to fix this but I can't /// figure it out so we're going to do this hacky thing to bring focus back to the terminal @@ -1161,7 +1116,6 @@ extension Ghostty { queue.async(execute: work) } } - #endif } // MARK: Surface Environment Keys @@ -1220,12 +1174,12 @@ extension FocusedValues { typealias Value = String } - var ghosttySurfaceCellSize: OSSize? { + var ghosttySurfaceCellSize: CGSize? { get { self[FocusedGhosttySurfaceCellSize.self] } set { self[FocusedGhosttySurfaceCellSize.self] = newValue } } struct FocusedGhosttySurfaceCellSize: FocusedValueKey { - typealias Value = OSSize + typealias Value = CGSize } } diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift index 6cd0ca76e..b5c73b9fe 100644 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/Surface View/SurfaceView_AppKit.swift @@ -2043,7 +2043,7 @@ extension Ghostty.SurfaceView: NSTextInputClient { x += cellSize.width * Double(range.location + range.length) } // Ghostty coordinates are in top-left (0, 0) so we have to convert to - // bottom-left since that is what UIKit expects + // bottom-left since that is what AppKit expects // when there's is no characters selected, // width should be 0 so that dictation indicator // can start in the right place diff --git a/macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift b/macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift deleted file mode 100644 index 6c1480b4e..000000000 --- a/macos/Sources/Ghostty/Surface View/SurfaceView_UIKit.swift +++ /dev/null @@ -1,84 +0,0 @@ -import SwiftUI -import GhosttyKit - -extension Ghostty { - /// The UIView implementation for a terminal surface. - class SurfaceView: OSSurfaceView { - // The current title of the surface as defined by the pty. This can be - // changed with escape codes. - @Published private(set) var title: String = "👻" - - /// True when the bell is active. This is set inactive on focus or event. - @Published var bell: Bool = false - - private(set) var _surface: ghostty_surface_t? - - override var surface: ghostty_surface_t? { - _surface - } - - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { - - // 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 - // can do SOMETHING. - super.init(id: uuid, frame: CGRect(x: 0, y: 0, width: 800, height: 600)) - - // Setup our surface. This will also initialize all the terminal IO. - let surface_cfg = baseConfig ?? SurfaceConfiguration() - let surface = surface_cfg.withCValue(view: self) { surface_cfg_c in - ghostty_surface_new(app, &surface_cfg_c) - } - guard let surface = surface else { - // TODO - return - } - self._surface = surface - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) is not supported for this view") - } - - deinit { - guard let surface = self.surface else { return } - ghostty_surface_free(surface) - } - - override func focusDidChange(_ focused: Bool) { - guard let surface = self.surface else { return } - ghostty_surface_set_focus(surface, focused) - - // On macOS 13+ we can store our continuous clock... - if focused { - focusInstant = ContinuousClock.now - } - } - - override func sizeDidChange(_ size: CGSize) { - guard let surface = self.surface else { return } - - // Ghostty wants to know the actual framebuffer size... It is very important - // here that we use "size" and NOT the view frame. If we're in the middle of - // an animation (i.e. a fullscreen animation), the frame will not yet be updated. - // The size represents our final size we're going for. - let scale = self.contentScaleFactor - ghostty_surface_set_content_scale(surface, scale, scale) - ghostty_surface_set_size( - surface, - UInt32(size.width * scale), - UInt32(size.height * scale) - ) - } - - // MARK: UIView - - override class var layerClass: AnyClass { - return CAMetalLayer.self - } - - override func didMoveToWindow() { - sizeDidChange(frame.size) - } - } -} diff --git a/macos/Sources/Helpers/Backport.swift b/macos/Sources/Helpers/Backport.swift index 0b6c61ac1..4f7123424 100644 --- a/macos/Sources/Helpers/Backport.swift +++ b/macos/Sources/Helpers/Backport.swift @@ -26,32 +26,23 @@ enum BackportKeyPressResult { extension Backport where Content: View { func pointerVisibility(_ v: BackportVisibility) -> some View { - #if canImport(AppKit) if #available(macOS 15, *) { return content.pointerVisibility(v.official) } else { return content } - #else - return content - #endif } func pointerStyle(_ style: BackportPointerStyle?) -> some View { - #if canImport(AppKit) if #available(macOS 15, *) { return content.pointerStyle(style?.official) } else { return content } - #else - return content - #endif } /// Backported onKeyPress that works on macOS 14+ and is a no-op on macOS 13. func onKeyPress(_ key: KeyEquivalent, action: @escaping (EventModifiers) -> BackportKeyPressResult) -> some View { - #if canImport(AppKit) if #available(macOS 14, *) { return content.onKeyPress(key, phases: .down, action: { keyPress in switch action(keyPress.modifiers) { @@ -62,9 +53,6 @@ extension Backport where Content: View { } else { return content } - #else - return content - #endif } } @@ -97,7 +85,6 @@ enum BackportPointerStyle { case resizeUpDown case resizeLeftRight - #if canImport(AppKit) @available(macOS 15, *) var official: PointerStyle { switch self { @@ -115,13 +102,11 @@ enum BackportPointerStyle { case .resizeLeftRight: return .columnResize } } - #endif } enum BackportNSGlassStyle { case regular, clear - #if canImport(AppKit) @available(macOS 26, *) var official: NSGlassEffectView.Style { switch self { @@ -129,11 +114,10 @@ enum BackportNSGlassStyle { case .clear: return .clear } } - #endif } -/// Backported `TextField` that supports text selection on macOS 26/iOS 18 and up. The `selection` -/// has no effect on versions below macOS 26/iOS 18. +/// Backported `TextField` that supports text selection on macOS 26 and up. The `selection` +/// has no effect on versions below macOS 26. /// /// Although the API is available from macOS 15, we force it to be 26. Because on macOS 15, /// SwiftUI will crash when deleting texts, even for this simple example. @@ -161,7 +145,7 @@ struct BackportSelectionTextField: View { } var body: some View { - if #available(iOS 18.0, macOS 26, *) { + if #available(macOS 26, *) { TextField( titleKey, text: _text, diff --git a/macos/Sources/Helpers/CrossKit.swift b/macos/Sources/Helpers/CrossKit.swift deleted file mode 100644 index c7b782072..000000000 --- a/macos/Sources/Helpers/CrossKit.swift +++ /dev/null @@ -1,58 +0,0 @@ -// This file is a helper to bridge some types that are effectively identical -// between AppKit and UIKit. - -import SwiftUI - -#if canImport(AppKit) - -import AppKit - -typealias OSView = NSView -typealias OSColor = NSColor -typealias OSSize = NSSize -typealias OSPasteboard = NSPasteboard -typealias OSApplication = NSApplication - -protocol OSViewRepresentable: NSViewRepresentable where NSViewType == OSViewType { - associatedtype OSViewType: NSView - func makeOSView(context: Context) -> OSViewType - func updateOSView(_ osView: OSViewType, context: Context) -} - -extension OSViewRepresentable { - func makeNSView(context: Context) -> OSViewType { - makeOSView(context: context) - } - - func updateNSView(_ nsView: OSViewType, context: Context) { - updateOSView(nsView, context: context) - } -} - -#elseif canImport(UIKit) - -import UIKit - -typealias OSView = UIView -typealias OSColor = UIColor -typealias OSSize = CGSize -typealias OSPasteboard = UIPasteboard -typealias OSApplication = UIApplication - -protocol OSViewRepresentable: UIViewRepresentable { - associatedtype OSViewType: UIView - func makeOSView(context: Context) -> OSViewType - func updateOSView(_ osView: OSViewType, context: Context) -} - -extension OSViewRepresentable { - func makeUIView(context: Context) -> OSViewType { - makeOSView(context: context) - } - - func updateUIView(_ uiView: OSViewType, context: Context) { - updateOSView(uiView, context: context) - } -} - -#endif diff --git a/macos/Sources/Helpers/Extensions/NSAppearance+Extension.swift b/macos/Sources/Helpers/Extensions/NSAppearance+Extension.swift index c45f37a62..53a30e3ee 100644 --- a/macos/Sources/Helpers/Extensions/NSAppearance+Extension.swift +++ b/macos/Sources/Helpers/Extensions/NSAppearance+Extension.swift @@ -1,4 +1,4 @@ -import Cocoa +import AppKit extension NSAppearance { /// Returns true if the appearance is some kind of dark. @@ -17,7 +17,7 @@ extension NSAppearance { self.init(named: .aqua) case "auto": - let color = OSColor(config.backgroundColor) + let color = NSColor(config.backgroundColor) if color.isLightColor { self.init(named: .aqua) } else { diff --git a/macos/Sources/Helpers/Extensions/OSColor+Extension.swift b/macos/Sources/Helpers/Extensions/OSColor+Extension.swift index 67246bcf5..65b13f98e 100644 --- a/macos/Sources/Helpers/Extensions/OSColor+Extension.swift +++ b/macos/Sources/Helpers/Extensions/OSColor+Extension.swift @@ -1,9 +1,10 @@ import Foundation +import AppKit #if !DOCK_TILE_PLUGIN import GhosttyKit #endif -extension OSColor { +extension NSColor { var isLightColor: Bool { return self.luminance > 0.5 } @@ -15,42 +16,20 @@ extension OSColor { var a: CGFloat = 0 // getRed:green:blue:alpha requires sRGB space - #if canImport(AppKit) guard let rgb = self.usingColorSpace(.sRGB) else { return 0 } - #else - let rgb = self - #endif rgb.getRed(&r, green: &g, blue: &b, alpha: &a) return (0.299 * r) + (0.587 * g) + (0.114 * b) } var hexString: String? { -#if canImport(AppKit) guard let rgb = usingColorSpace(.deviceRGB) else { return nil } let red = Int(rgb.redComponent * 255) let green = Int(rgb.greenComponent * 255) let blue = Int(rgb.blueComponent * 255) return String(format: "#%02X%02X%02X", red, green, blue) -#elseif canImport(UIKit) - var red: CGFloat = 0 - var green: CGFloat = 0 - var blue: CGFloat = 0 - var alpha: CGFloat = 0 - guard self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) else { - return nil - } - - // Convert to 0–255 range - let r = Int(red * 255) - let g = Int(green * 255) - let b = Int(blue * 255) - - // Format to hexadecimal - return String(format: "#%02X%02X%02X", r, g, b) -#endif } - /// Create an OSColor from a hex string. + /// Create an NSColor from a hex string. convenience init?(hex: String) { var cleanedHex = hex.trimmingCharacters(in: .whitespacesAndNewlines) @@ -81,10 +60,10 @@ extension OSColor { self.init(red: red, green: green, blue: blue, alpha: alpha) } - func darken(by amount: CGFloat) -> OSColor { + func darken(by amount: CGFloat) -> NSColor { var h: CGFloat = 0, s: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0 self.getHue(&h, saturation: &s, brightness: &b, alpha: &a) - return OSColor( + return NSColor( hue: h, saturation: s, brightness: min(b * (1 - amount), 1), @@ -95,7 +74,7 @@ extension OSColor { // MARK: Ghostty Types #if !DOCK_TILE_PLUGIN -extension OSColor { +extension NSColor { /// Create a color from a Ghostty color. convenience init(ghostty: ghostty_config_color_s) { let red = Double(ghostty.r) / 255 diff --git a/macos/Sources/Helpers/Extensions/OSPasteboard+Extension.swift b/macos/Sources/Helpers/Extensions/OSPasteboard+Extension.swift index 6a59124e1..4be01bbd4 100644 --- a/macos/Sources/Helpers/Extensions/OSPasteboard+Extension.swift +++ b/macos/Sources/Helpers/Extensions/OSPasteboard+Extension.swift @@ -1,9 +1,8 @@ -#if canImport(AppKit) +import AppKit -/// Normalizes the interface between NSPasteboard and UIPasteboard for working with pasteboard -/// strings. -extension OSPasteboard { - @MainActor static let find = OSPasteboard(name: .find) +/// Convenience interface for working with pasteboard strings. +extension NSPasteboard { + @MainActor static let find = NSPasteboard(name: .find) /// The pasteboard's current string value. @MainActor var string: String? { @@ -18,11 +17,3 @@ extension OSPasteboard { } } } - -#elseif canImport(UIKit) - -extension OSPasteboard { - static let find = OSPasteboard.withUniqueName() -} - -#endif diff --git a/macos/Sources/Helpers/Extensions/String+Extension.swift b/macos/Sources/Helpers/Extensions/String+Extension.swift index 4fa61cd78..beb5d87e7 100644 --- a/macos/Sources/Helpers/Extensions/String+Extension.swift +++ b/macos/Sources/Helpers/Extensions/String+Extension.swift @@ -7,7 +7,6 @@ extension String { return self.prefix(maxLength) + trailing } -#if canImport(AppKit) func temporaryFile(_ filename: String = "temp") -> URL { let url = FileManager.default.temporaryDirectory .appendingPathComponent(filename) @@ -25,7 +24,6 @@ extension String { } return self } -#endif /// Converts a four-character ASCII string to its `FourCharCode` (`UInt32`) value. var fourCharCode: UInt32 { diff --git a/macos/Sources/Helpers/UntrustedURLAlert.swift b/macos/Sources/Helpers/UntrustedURLAlert.swift index 587d47fdd..0c93b7c40 100644 --- a/macos/Sources/Helpers/UntrustedURLAlert.swift +++ b/macos/Sources/Helpers/UntrustedURLAlert.swift @@ -1,4 +1,3 @@ -#if os(macOS) import AppKit /// Presents decisions for untrusted URLs at the AppKit boundary. @@ -103,4 +102,3 @@ enum UntrustedURLAlert { return scrollView } } -#endif diff --git a/macos/Tests/Ghostty/Surface View/SurfaceView+SearchStateTests.swift b/macos/Tests/Ghostty/Surface View/SurfaceView+SearchStateTests.swift index 609bc48f7..20894cf54 100644 --- a/macos/Tests/Ghostty/Surface View/SurfaceView+SearchStateTests.swift +++ b/macos/Tests/Ghostty/Surface View/SurfaceView+SearchStateTests.swift @@ -8,7 +8,7 @@ import Testing typealias StartSearch = Ghostty.Action.StartSearch /// A unique pasteboard for each test case prevents flakiness. - let pasteboard = OSPasteboard.withUniqueName() + let pasteboard = NSPasteboard.withUniqueName() init() { pasteboard.setString("pb", forType: .string)