add "Set Ghostty as Default Terminal App" on macOS (#10810)

This PR enables iTerm2-like one button "Set Ghostty as Default Terminal
App" functionality on macOS, making it easier to open a directory in
Ghostty, run shell scripts when mouse clicking, etc.
This commit is contained in:
Mitchell Hashimoto
2026-02-17 21:04:55 -08:00
committed by GitHub
5 changed files with 66 additions and 0 deletions

View File

@@ -147,6 +147,7 @@
Features/Update/UpdatePopoverView.swift,
Features/Update/UpdateSimulator.swift,
Features/Update/UpdateViewModel.swift,
"Ghostty/Extensions/NSWorkspace+Ghostty.swift",
"Ghostty/FullscreenMode+Extension.swift",
Ghostty/Ghostty.Error.swift,
Ghostty/Ghostty.Event.swift,

View File

@@ -65,6 +65,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuReturnToDefaultSize: NSMenuItem?
@IBOutlet private var menuFloatOnTop: NSMenuItem?
@IBOutlet private var menuUseAsDefault: NSMenuItem?
@IBOutlet private var menuSetAsDefaultTerminal: NSMenuItem?
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
@@ -577,6 +578,7 @@ class AppDelegate: NSObject,
self.menuChangeTabTitle?.setImageIfDesired(systemSymbolName: "pencil.line")
self.menuTerminalInspector?.setImageIfDesired(systemSymbolName: "scope")
self.menuReadonly?.setImageIfDesired(systemSymbolName: "eye.fill")
self.menuSetAsDefaultTerminal?.setImageIfDesired(systemSymbolName: "star.fill")
self.menuToggleFullScreen?.setImageIfDesired(systemSymbolName: "square.arrowtriangle.4.outward")
self.menuToggleVisibility?.setImageIfDesired(systemSymbolName: "eye")
self.menuZoomSplit?.setImageIfDesired(systemSymbolName: "arrow.up.left.and.arrow.down.right")
@@ -1292,6 +1294,21 @@ extension AppDelegate {
ud.removeObject(forKey: key)
}
}
@IBAction func setAsDefaultTerminal(_ sender: NSMenuItem) {
do {
try NSWorkspace.shared.setGhosttyAsDefaultTerminal()
// Success - menu state will automatically update via validateMenuItem
} catch {
// Show error dialog
let alert = NSAlert()
alert.messageText = "Failed to Set Default Terminal"
alert.informativeText = "Ghostty could not be set as the default terminal application.\n\nError: \(error.localizedDescription)"
alert.alertStyle = .warning
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
}
// MARK: NSMenuItemValidation
@@ -1299,6 +1316,12 @@ extension AppDelegate {
extension AppDelegate: NSMenuItemValidation {
func validateMenuItem(_ item: NSMenuItem) -> Bool {
switch item.action {
case #selector(setAsDefaultTerminal(_:)):
// Check if Ghostty is already the default terminal
let isDefault = NSWorkspace.shared.isGhosttyDefaultTerminal
// Disable menu item if already default (option A)
return !isDefault
case #selector(floatOnTop(_:)),
#selector(useAsDefault(_:)):
// Float on top items only active if the key window is a primary

View File

@@ -60,6 +60,7 @@
<outlet property="menuSelectSplitRight" destination="upj-mc-L7X" id="nLY-o1-lky"/>
<outlet property="menuSelectionForSearch" destination="TDN-42-Bu7" id="M04-1K-vze"/>
<outlet property="menuServices" destination="aQe-vS-j8Q" id="uWQ-Wo-T1L"/>
<outlet property="menuSetAsDefaultTerminal" destination="b1t-oB-7MI" id="6Eu-5G-OPo"/>
<outlet property="menuSplitDown" destination="UDZ-4y-6xL" id="ptr-mj-Azh"/>
<outlet property="menuSplitLeft" destination="Ppv-GP-lQU" id="Xd5-Cd-Jut"/>
<outlet property="menuSplitRight" destination="VUR-Ld-nLx" id="RxO-Zw-ovb"/>
@@ -109,6 +110,12 @@
<action selector="toggleSecureInput:" target="bbz-4X-AYv" id="vWx-z8-5Sy"/>
</connections>
</menuItem>
<menuItem title="Make Ghostty the Default Terminal" id="b1t-oB-7MI" userLabel="Set Ghostty as Default Terminal App">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="setAsDefaultTerminal:" target="bbz-4X-AYv" id="QHh-CA-Qho"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
<menuItem title="Services" id="rJe-5J-bwL">
<modifierMask key="keyEquivalentModifierMask"/>

View File

@@ -0,0 +1,34 @@
import AppKit
import UniformTypeIdentifiers
extension NSWorkspace {
/// Checks if Ghostty is the default terminal application.
/// - Returns: True if Ghostty is the default application for handling public.unix-executable files.
var isGhosttyDefaultTerminal: Bool {
let ghosttyURL = Bundle.main.bundleURL
guard let defaultAppURL = defaultApplicationURL(forContentType: "public.unix-executable") else {
return false
}
// Compare bundle paths
return ghosttyURL.path == defaultAppURL.path
}
/// Sets Ghostty as the default terminal application.
/// - Throws: An error if the application bundle cannot be located or if setting the default fails.
func setGhosttyAsDefaultTerminal() throws {
let ghosttyURL = Bundle.main.bundleURL
// Create UTType for unix executables
guard let unixExecutableType = UTType("public.unix-executable") else {
throw NSError(
domain: "com.mitchellh.ghostty",
code: 2,
userInfo: [NSLocalizedDescriptionKey: "Could not create UTType for public.unix-executable"]
)
}
// Use NSWorkspace API to set the default application
// This API is available on macOS 12.0+, Ghostty supports 13.0+, so it's compatible
setDefaultApplication(at: ghosttyURL, toOpen: unixExecutableType)
}
}

View File

@@ -26,4 +26,5 @@ extension NSWorkspace {
guard let uti = UTType(filenameExtension: ext) else { return nil}
return defaultApplicationURL(forContentType: uti.identifier)
}
}