macOS: remove iOS target and clean up cross platform checks (#13759)

As discussed in Discord: Ghostty internal support for iOS is not
important and we verify iOS compatibility for `libghostty-vt` through
compilation.

Diff is **BIG**, but it contains mostly cleanup and didn't touch macOS's
implementation (except for some renaming).

### AI Disclosure

Claude did batch removal for me, I manually reviewed them and ran
locally for macOS.
This commit is contained in:
Mitchell Hashimoto
2026-08-12 06:10:11 -07:00
committed by GitHub
32 changed files with 38 additions and 826 deletions

View File

@@ -1164,14 +1164,6 @@ jobs:
COMPILATION_CACHE_CAS_PATH=/Users/runner/Library/Developer/Xcode/DerivedData/CompilationCache.noindex \
COMPILATION_CACHE_KEEP_CAS_DIRECTORY=YES
# Build the iOS target without code signing just to verify it works.
- name: Build Ghostty iOS
run: |
cd macos
xcodebuild -target Ghostty-iOS "CODE_SIGNING_ALLOWED=NO" \
COMPILATION_CACHE_CAS_PATH=/Users/runner/Library/Developer/Xcode/DerivedData/CompilationCache.noindex \
COMPILATION_CACHE_KEEP_CAS_DIRECTORY=YES
build-macos-xcode-27:
runs-on: namespace-profile-ghostty-macos-tahoe
needs: test

View File

@@ -15,10 +15,7 @@
8F3A9B4C2FA6B88000A18D13 /* Ghostty.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 8F3A9B4B2FA6B88000A18D13 /* Ghostty.sdef */; };
9351BE8E3D22937F003B3499 /* nvim in Resources */ = {isa = PBXBuildFile; fileRef = 9351BE8E2D22937F003B3499 /* nvim */; };
A51BFC272B30F1B800E92F16 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = A51BFC262B30F1B800E92F16 /* Sparkle */; };
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
A546F1142D7B68D7003B11A0 /* locale in Resources */ = {isa = PBXBuildFile; fileRef = A546F1132D7B68D7003B11A0 /* locale */; };
A553F4132E06EB1600257779 /* Ghostty.icon in Resources */ = {isa = PBXBuildFile; fileRef = A553F4122E06EB1600257779 /* Ghostty.icon */; };
A553F4142E06EB1600257779 /* Ghostty.icon in Resources */ = {isa = PBXBuildFile; fileRef = A553F4122E06EB1600257779 /* Ghostty.icon */; };
A56B880B2A840447007A0E29 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A56B880A2A840447007A0E29 /* Carbon.framework */; };
A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
@@ -89,7 +86,6 @@
A5B30531299BEAAA0047F10C /* Ghostty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Ghostty.app; sourceTree = BUILT_PRODUCTS_DIR; };
A5B30538299BEAAB0047F10C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
A5B3053D299BEAAB0047F10C /* Ghostty.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Ghostty.entitlements; sourceTree = "<group>"; };
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Ghostty-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = "<group>"; };
FC5218F92D10FFC7004C93E0 /* zsh */ = {isa = PBXFileReference; lastKnownFileType = folder; name = zsh; path = "../zig-out/share/zsh"; sourceTree = "<group>"; };
FC9ABA9B2D0F538D0020D4C8 /* bash-completion */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "bash-completion"; path = "../zig-out/share/bash-completion"; sourceTree = "<group>"; };
@@ -106,165 +102,16 @@
"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",
);
target = 8193244C2F24E6C000A9ED8F /* DockTilePlugin */;
};
81F82CB02E8281F5001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/macOS/AppDelegate.swift,
"App/macOS/AppDelegate+Ghostty.swift",
App/macOS/CommandLineOpenFileFilter.swift,
App/macOS/main.swift,
App/macOS/MainMenu.xib,
Features/About/About.xib,
Features/About/AboutController.swift,
Features/About/AboutView.swift,
Features/About/AboutViewModel.swift,
Features/About/CyclingIconView.swift,
"Features/App Intents/CloseTerminalIntent.swift",
"Features/App Intents/CommandPaletteIntent.swift",
"Features/App Intents/Entities/CommandEntity.swift",
"Features/App Intents/Entities/TerminalEntity.swift",
"Features/App Intents/FocusTerminalIntent.swift",
"Features/App Intents/GetTerminalDetailsIntent.swift",
"Features/App Intents/GhosttyIntentError.swift",
"Features/App Intents/InputIntent.swift",
"Features/App Intents/IntentPermission.swift",
"Features/App Intents/KeybindIntent.swift",
"Features/App Intents/NewTerminalIntent.swift",
"Features/App Intents/QuickTerminalIntent.swift",
"Features/AppleScript/AppDelegate+AppleScript.swift",
"Features/AppleScript/Ghostty.Input.Mods+AppleScript.swift",
Features/AppleScript/ScriptInputTextCommand.swift,
Features/AppleScript/ScriptKeyEventCommand.swift,
Features/AppleScript/ScriptMouseButtonCommand.swift,
Features/AppleScript/ScriptMousePosCommand.swift,
Features/AppleScript/ScriptMouseScrollCommand.swift,
Features/AppleScript/ScriptRecord.swift,
Features/AppleScript/ScriptSurfaceConfiguration.swift,
Features/AppleScript/ScriptTab.swift,
Features/AppleScript/ScriptTerminal.swift,
Features/AppleScript/ScriptWindow.swift,
Features/ClipboardConfirmation/ClipboardConfirmation.xib,
Features/ClipboardConfirmation/ClipboardConfirmationController.swift,
Features/ClipboardConfirmation/ClipboardConfirmationView.swift,
"Features/Command Palette/CommandPalette.swift",
"Features/Command Palette/TerminalCommandPalette.swift",
"Features/Custom App Icon/AppIcon.swift",
"Features/Custom App Icon/ColorizedGhosttyIcon.swift",
"Features/Custom App Icon/ColorizedGhosttyIconImage.swift",
"Features/Custom App Icon/ColorizedGhosttyIconView.swift",
"Features/Custom App Icon/DockTilePlugin.swift",
"Features/Custom App Icon/Extensions/Notification+AppIcon.swift",
"Features/Custom App Icon/Extensions/UserDefaults+AppIcon.swift",
"Features/Global Keybinds/GlobalEventTap.swift",
Features/QuickTerminal/QuickTerminal.xib,
Features/QuickTerminal/QuickTerminalController.swift,
Features/QuickTerminal/QuickTerminalPosition.swift,
Features/QuickTerminal/QuickTerminalRestorableState.swift,
Features/QuickTerminal/QuickTerminalScreen.swift,
Features/QuickTerminal/QuickTerminalScreenStateCache.swift,
Features/QuickTerminal/QuickTerminalSize.swift,
Features/QuickTerminal/QuickTerminalSpaceBehavior.swift,
Features/QuickTerminal/QuickTerminalWindow.swift,
"Features/Secure Input/SecureInput.swift",
"Features/Secure Input/SecureInputOverlay.swift",
Features/Services/ServiceProvider.swift,
Features/Settings/ConfigurationErrors.xib,
Features/Settings/ConfigurationErrorsController.swift,
Features/Settings/ConfigurationErrorsView.swift,
Features/Settings/SettingsView.swift,
Features/Splits/SplitTree.swift,
Features/Splits/SplitView.Divider.swift,
Features/Splits/SplitView.swift,
Features/Splits/TerminalSplitTreeView.swift,
Features/Terminal/BaseTerminalController.swift,
Features/Terminal/ErrorView.swift,
Features/Terminal/TerminalController.swift,
Features/Terminal/TerminalRestorable.swift,
"Features/Terminal/TerminalRestorableState+InteralState.swift",
Features/Terminal/TerminalTabColor.swift,
Features/Terminal/TerminalView.swift,
Features/Terminal/TerminalViewContainer.swift,
"Features/Terminal/Window Styles/HiddenTitlebarTerminalWindow.swift",
"Features/Terminal/Window Styles/Terminal.xib",
"Features/Terminal/Window Styles/TerminalHiddenTitlebar.xib",
"Features/Terminal/Window Styles/TerminalTabsTitlebarTahoe.xib",
"Features/Terminal/Window Styles/TerminalTabsTitlebarVentura.xib",
"Features/Terminal/Window Styles/TerminalTransparentTitlebar.xib",
"Features/Terminal/Window Styles/TerminalWindow.swift",
"Features/Terminal/Window Styles/TitlebarTabsTahoeTerminalWindow.swift",
"Features/Terminal/Window Styles/TitlebarTabsVenturaTerminalWindow.swift",
"Features/Terminal/Window Styles/TransparentTitlebarTerminalWindow.swift",
Features/Update/UpdateBadge.swift,
Features/Update/UpdateController.swift,
Features/Update/UpdateDelegate.swift,
Features/Update/UpdateDriver.swift,
Features/Update/UpdatePill.swift,
Features/Update/UpdatePopoverView.swift,
Features/Update/UpdateSimulator.swift,
Features/Update/UpdateViewModel.swift,
"Ghostty/FullscreenMode+Extension.swift",
Ghostty/Ghostty.Error.swift,
Ghostty/Ghostty.Event.swift,
Ghostty/Ghostty.Input.swift,
Ghostty/Ghostty.MenuShortcutManager.swift,
Ghostty/Ghostty.Surface.swift,
"Ghostty/NSEvent+Extension.swift",
"Ghostty/Surface View/InspectorView.swift",
"Ghostty/Surface View/SurfaceDragSource.swift",
"Ghostty/Surface View/SurfaceGrabHandle.swift",
"Ghostty/Surface View/SurfaceScrollView.swift",
"Ghostty/Surface View/SurfaceView_AppKit.swift",
Helpers/AppInfo.swift,
Helpers/CodableBridge.swift,
Helpers/Cursor.swift,
Helpers/ExpiringUndoManager.swift,
"Helpers/Extensions/Double+Extension.swift",
"Helpers/Extensions/EventModifiers+Extension.swift",
"Helpers/Extensions/FileHandle+Extension.swift",
"Helpers/Extensions/KeyboardShortcut+Extension.swift",
"Helpers/Extensions/NSAppearance+Extension.swift",
"Helpers/Extensions/NSApplication+Extension.swift",
"Helpers/Extensions/NSColor+Extension.swift",
"Helpers/Extensions/NSImage+Extension.swift",
"Helpers/Extensions/NSMenu+Extension.swift",
"Helpers/Extensions/NSMenuItem+Extension.swift",
"Helpers/Extensions/NSPasteboard+Extension.swift",
"Helpers/Extensions/NSScreen+Extension.swift",
"Helpers/Extensions/NSView+Extension.swift",
"Helpers/Extensions/NSWindow+Extension.swift",
"Helpers/Extensions/NSWorkspace+Extension.swift",
"Helpers/Extensions/Transferable+Extension.swift",
"Helpers/Extensions/UndoManager+Extension.swift",
"Helpers/Extensions/View+Extension.swift",
Helpers/Fullscreen.swift,
Helpers/HostingWindow.swift,
Helpers/KeyboardLayout.swift,
Helpers/LastWindowPosition.swift,
Helpers/MetalView.swift,
Helpers/NonDraggableHostingView.swift,
Helpers/ObjCExceptionCatcher.m,
Helpers/PermissionRequest.swift,
Helpers/Private/CGS.swift,
Helpers/Private/Dock.swift,
Helpers/TabGroupCloseCoordinator.swift,
Helpers/TabTitleEditor.swift,
Helpers/VibrantLayer.m,
);
target = A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */;
};
81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
App/iOS/iOSApp.swift,
"Features/Custom App Icon/DockTilePlugin.swift",
"Ghostty/Surface View/SurfaceView_UIKit.swift",
);
target = A5B30530299BEAAA0047F10C /* Ghostty */;
};
@@ -272,7 +119,7 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
810ACCA02E9D3302004F8F92 /* GhosttyUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = GhosttyUITests; sourceTree = "<group>"; };
81F82BC72E82815D001EDFA7 /* Sources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 81F82CB02E8281F5001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 8193245D2F24E80800A9ED8F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Sources; sourceTree = "<group>"; };
81F82BC72E82815D001EDFA7 /* Sources */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (81F82CB12E8281F9001EDFA7 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 8193245D2F24E80800A9ED8F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Sources; sourceTree = "<group>"; };
A54F45F42E1F047A0046BD5C /* Tests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Tests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
@@ -308,14 +155,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A5D4499A2B53AE7B000F5B83 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
@@ -359,7 +198,6 @@
isa = PBXGroup;
children = (
A5B30531299BEAAA0047F10C /* Ghostty.app */,
A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */,
A54F45F32E1F047A0046BD5C /* GhosttyTests.xctest */,
810ACC9F2E9D3301004F8F92 /* GhosttyUITests.xctest */,
8193244D2F24E6C000A9ED8F /* DockTilePlugin.plugin */,
@@ -470,26 +308,6 @@
productReference = A5B30531299BEAAA0047F10C /* Ghostty.app */;
productType = "com.apple.product-type.application";
};
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = A5D449AB2B53AE7B000F5B83 /* Build configuration list for PBXNativeTarget "Ghostty-iOS" */;
buildPhases = (
A5D449992B53AE7B000F5B83 /* Sources */,
A5D4499A2B53AE7B000F5B83 /* Frameworks */,
A5D4499B2B53AE7B000F5B83 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
81F82BC72E82815D001EDFA7 /* Sources */,
);
name = "Ghostty-iOS";
productName = "Ghostty-iOS";
productReference = A5D4499D2B53AE7B000F5B83 /* Ghostty-iOS.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -515,9 +333,6 @@
CreatedOnToolsVersion = 14.2;
LastSwiftMigration = 1510;
};
A5D4499C2B53AE7B000F5B83 = {
CreatedOnToolsVersion = 15.2;
};
};
};
buildConfigurationList = A5B3052C299BEAAA0047F10C /* Build configuration list for PBXProject "Ghostty" */;
@@ -537,7 +352,6 @@
projectRoot = "";
targets = (
A5B30530299BEAAA0047F10C /* Ghostty */,
A5D4499C2B53AE7B000F5B83 /* Ghostty-iOS */,
8193244C2F24E6C000A9ED8F /* DockTilePlugin */,
A54F45F22E1F047A0046BD5C /* GhosttyTests */,
810ACC9E2E9D3301004F8F92 /* GhosttyUITests */,
@@ -588,15 +402,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A5D4499B2B53AE7B000F5B83 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */,
A553F4132E06EB1600257779 /* Ghostty.icon in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
@@ -651,13 +456,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
A5D449992B53AE7B000F5B83 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
@@ -784,7 +582,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/ghostty-bridging-header.h";
SWIFT_VERSION = 5.0;
};
name = ReleaseLocal;
@@ -1181,7 +979,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty.debug;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/ghostty-bridging-header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
@@ -1236,128 +1034,11 @@
PRODUCT_BUNDLE_IDENTIFIER = com.mitchellh.ghostty;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/macOS/ghostty-bridging-header.h";
SWIFT_OBJC_BRIDGING_HEADER = "Sources/App/ghostty-bridging-header.h";
SWIFT_VERSION = 5.0;
};
name = Release;
};
A5D449A82B53AE7B000F5B83 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Ghostty;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 0.1;
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
A5D449A92B53AE7B000F5B83 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Ghostty;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 0.1;
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
A5D449AA2B53AE7B000F5B83 /* ReleaseLocal */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Ghostty;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = "";
ENABLE_PREVIEWS = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = Ghostty;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 0.1;
"OTHER_LDFLAGS[arch=*]" = "-lstdc++";
PRODUCT_BUNDLE_IDENTIFIER = "com.mitchellh.ghostty-ios";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = ReleaseLocal;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
@@ -1411,16 +1092,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseLocal;
};
A5D449AB2B53AE7B000F5B83 /* Build configuration list for PBXNativeTarget "Ghostty-iOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
A5D449A82B53AE7B000F5B83 /* Debug */,
A5D449A92B53AE7B000F5B83 /* Release */,
A5D449AA2B53AE7B000F5B83 /* ReleaseLocal */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = ReleaseLocal;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */

View File

@@ -1,50 +0,0 @@
import SwiftUI
import GhosttyKit
@main
struct Ghostty_iOSApp: App {
@StateObject private var ghostty_app: Ghostty.App
init() {
if ghostty_init(UInt(CommandLine.argc), CommandLine.unsafeArgv) != GHOSTTY_SUCCESS {
preconditionFailure("Initialize ghostty backend failed")
}
_ghostty_app = StateObject(wrappedValue: Ghostty.App())
}
var body: some Scene {
WindowGroup {
iOS_GhosttyTerminal()
.environmentObject(ghostty_app)
}
}
}
struct iOS_GhosttyTerminal: View {
@EnvironmentObject private var ghostty_app: Ghostty.App
var body: some View {
ZStack {
// Make sure that our background color extends to all parts of the screen
Color(ghostty_app.config.backgroundColor).ignoresSafeArea()
Ghostty.Terminal()
}
}
}
struct iOS_GhosttyInitView: View {
@EnvironmentObject private var ghostty_app: Ghostty.App
var body: some View {
VStack {
Image("AppIconImage")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(maxHeight: 96)
Text("Ghostty")
Text("State: \(ghostty_app.readiness.rawValue)")
}
.padding()
}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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")

View File

@@ -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 {

View File

@@ -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<CChar>?,
state: UnsafeMutableRawPointer?,
request: ghostty_clipboard_request_e
) {}
static func writeClipboard(
_ userdata: UnsafeMutableRawPointer?,
location: ghostty_clipboard_e,
content: UnsafePointer<ghostty_clipboard_content_s>?,
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
}
}

View File

@@ -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<Int8>?
@@ -310,21 +303,9 @@ extension Ghostty {
nil
}
}
#else
var windowFullscreen: Bool {
guard let config = self.config else { return false }
var v: UnsafePointer<Int8>?
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<Int8>?
@@ -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<Int8>?
@@ -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 }

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 scrollers appearance matches the surface's background color.
scrollView.appearance = NSAppearance(named: hasLightBackground ? .aqua : .darkAqua)
updateTrackingAreas()

View File

@@ -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
}

View File

@@ -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

View File

@@ -4,35 +4,6 @@ import GhosttyKit
import System
extension Ghostty {
/// Render a terminal for the active app in the environment.
struct Terminal: View {
@EnvironmentObject private var ghostty: Ghostty.App
var body: some View {
if let app = self.ghostty.app {
SurfaceForApp(app) { surfaceView in
SurfaceWrapper(surfaceView: surfaceView)
}
}
}
}
/// Yields a SurfaceView for a ghostty app that can then be used however you want.
struct SurfaceForApp<Content: View>: View {
let content: ((SurfaceView) -> Content)
@StateObject private var surfaceView: SurfaceView
init(_ app: ghostty_app_t, @ViewBuilder content: @escaping ((SurfaceView) -> Content)) {
_surfaceView = StateObject(wrappedValue: SurfaceView(app))
self.content = content
}
var body: some View {
content(surfaceView)
}
}
struct SurfaceWrapper: View {
// The surface to create a view for. This must be created upstream. As long as this
// remains the same, the surface that is being rendered remains the same.
@@ -48,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
@@ -68,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 }
@@ -91,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 {
@@ -118,7 +83,6 @@ extension Ghostty {
.transition(.opacity)
}
#if canImport(AppKit)
// Readonly indicator badge
if surfaceView.readonly {
ReadonlyBadge {
@@ -132,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.
@@ -148,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 &&
@@ -157,7 +119,6 @@ extension Ghostty {
windowFocus {
SecureInputOverlay()
}
#endif
// Search overlay
if let searchState = surfaceView.searchState {
@@ -200,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
}
}
}
@@ -413,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
@@ -423,7 +380,6 @@ extension Ghostty {
.onSubmit {
_ = surfaceView.navigateSearchToNext()
}
#if canImport(AppKit)
.onExitCommand {
if searchState.needle.text.isEmpty {
onClose()
@@ -431,7 +387,6 @@ extension Ghostty {
Ghostty.moveFocus(to: surfaceView)
}
}
#endif
.backport.onKeyPress(.return) { modifiers in
if modifiers.contains(.shift) {
_ = surfaceView.navigateSearchToPrevious()
@@ -506,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)
@@ -586,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
@@ -600,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
@@ -615,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
@@ -685,25 +628,11 @@ extension Ghostty {
func withCValue<T>(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
@@ -754,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 {
@@ -972,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 {
@@ -1136,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
@@ -1190,7 +1116,6 @@ extension Ghostty {
queue.async(execute: work)
}
}
#endif
}
// MARK: Surface Environment Keys
@@ -1249,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
}
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -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,

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 0255 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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)