macos: implement service provider

This commit is contained in:
Mitchell Hashimoto
2023-10-04 12:11:23 -07:00
parent 5018bd42c8
commit a040e807e8
4 changed files with 126 additions and 1 deletions

View File

@ -22,6 +22,47 @@
<key>GHOSTTY_MAC_APP</key>
<string>1</string>
</dict>
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>New Ghostty Tab Here</string>
</dict>
<key>NSMessage</key>
<string>openTab</string>
<key>NSRequiredContext</key>
<dict>
<key>NSTextContent</key>
<string>FilePath</string>
</dict>
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
<string>public.plain-text</string>
</array>
</dict>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>New Ghostty Window Here</string>
</dict>
<key>NSMessage</key>
<string>openWindow</string>
<key>NSRequiredContext</key>
<dict>
<key>NSTextContent</key>
<string>FilePath</string>
</dict>
<key>NSSendTypes</key>
<array>
<string>NSFilenamesPboardType</string>
<string>public.plain-text</string>
</array>
</dict>
</array>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSAppleEventsUsageDescription</key>

View File

@ -23,6 +23,7 @@
A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */; };
A56B880B2A840447007A0E29 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A56B880A2A840447007A0E29 /* Carbon.framework */; };
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */; };
A571AB1D2A206FCF00248498 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
A5A1F8852A489D6800D1E8BC /* terminfo in Resources */ = {isa = PBXBuildFile; fileRef = A5A1F8842A489D6800D1E8BC /* terminfo */; };
@ -55,6 +56,7 @@
A55B7BBD29B701360055DE60 /* Ghostty.SplitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.SplitView.swift; sourceTree = "<group>"; };
A56B880A2A840447007A0E29 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Shell.swift; sourceTree = "<group>"; };
A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceProvider.swift; sourceTree = "<group>"; };
A571AB1C2A206FC600248498 /* Ghostty-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Ghostty-Info.plist"; sourceTree = "<group>"; };
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
A5A1F8842A489D6800D1E8BC /* terminfo */ = {isa = PBXFileReference; lastKnownFileType = folder; name = terminfo; path = "../zig-out/share/terminfo"; sourceTree = "<group>"; };
@ -89,6 +91,7 @@
A53426362A7DC53000EBB7A2 /* Features */ = {
isa = PBXGroup;
children = (
A56D58872ACDE6BE00508D2C /* Services */,
A53426372A7DC53A00EBB7A2 /* Primary Window */,
A534263E2A7DCC5800EBB7A2 /* Settings */,
);
@ -156,6 +159,14 @@
path = Ghostty;
sourceTree = "<group>";
};
A56D58872ACDE6BE00508D2C /* Services */ = {
isa = PBXGroup;
children = (
A56D58882ACDE6CA00508D2C /* ServiceProvider.swift */,
);
path = Services;
sourceTree = "<group>";
};
A5A1F8862A489D7400D1E8BC /* Resources */ = {
isa = PBXGroup;
children = (
@ -279,6 +290,7 @@
files = (
A53426392A7DC55C00EBB7A2 /* PrimaryWindowManager.swift in Sources */,
85DE1C922A6A3DCA00493853 /* PrimaryWindow.swift in Sources */,
A56D58892ACDE6CA00508D2C /* ServiceProvider.swift in Sources */,
A5278A9B2AA05B2600CD3039 /* Ghostty.Input.swift in Sources */,
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */,
A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */,

View File

@ -40,7 +40,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
private var ghostty: Ghostty.AppState = Ghostty.AppState()
/// Manages windows and tabs, ensuring they're allocated/deallocated correctly
private var windowManager: PrimaryWindowManager!
var windowManager: PrimaryWindowManager!
override init() {
super.init()
@ -72,6 +72,10 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp
// Initial config loading
configDidReload(ghostty)
// Register our service provider. This must happen after everything
// else is initialized.
NSApp.servicesProvider = ServiceProvider()
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {

View File

@ -0,0 +1,68 @@
import Foundation
import AppKit
class ServiceProvider: NSObject {
static private let errorNoString = NSString(string: "Could not load any text from the clipboard.")
/// The target for an open operation
enum OpenTarget {
case tab
case window
}
@objc func openTab(
_ pasteboard: NSPasteboard,
userData: String?,
error: AutoreleasingUnsafeMutablePointer<NSString>
) {
guard let str = pasteboard.string(forType: .string) else {
error.pointee = Self.errorNoString
return
}
openTerminal(str, target: .tab)
}
@objc func openWindow(
_ pasteboard: NSPasteboard,
userData: String?,
error: AutoreleasingUnsafeMutablePointer<NSString>
) {
guard let str = pasteboard.string(forType: .string) else {
error.pointee = Self.errorNoString
return
}
openTerminal(str, target: .window)
}
private func openTerminal(_ path: String, target: OpenTarget) {
guard let delegateRaw = NSApp.delegate else { return }
guard let delegate = delegateRaw as? AppDelegate else { return }
guard let windowManager = delegate.windowManager else { return }
// We only open in directories.
var isDirectory = ObjCBool(true)
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) else { return }
guard isDirectory.boolValue else { return }
// Build our config
var config = Ghostty.SurfaceConfiguration()
config.workingDirectory = path
// If we don't have a window open through the window manager, we launch
// a new window even if they requested a tab.
guard let mainWindow = windowManager.mainWindow else {
windowManager.addNewWindow(withBaseConfig: config)
return
}
switch (target) {
case .window:
windowManager.addNewWindow(withBaseConfig: config)
case .tab:
windowManager.addNewTab(to: mainWindow, withBaseConfig: config)
}
}
}