124 lines
3.9 KiB
Swift
124 lines
3.9 KiB
Swift
import AppKit
|
|
|
|
enum TerminalApp: String, CaseIterable, Identifiable {
|
|
static let pickerIconSize = NSSize(width: 16, height: 16)
|
|
|
|
case terminal
|
|
case iTerm
|
|
|
|
var id: String {
|
|
self.rawValue
|
|
}
|
|
|
|
var label: String {
|
|
switch self {
|
|
case .terminal: "Terminal"
|
|
case .iTerm: "iTerm"
|
|
}
|
|
}
|
|
|
|
var bundleIdentifier: String {
|
|
switch self {
|
|
case .terminal: "com.apple.Terminal"
|
|
case .iTerm: "com.googlecode.iterm2"
|
|
}
|
|
}
|
|
|
|
var isInstalled: Bool {
|
|
self.isInstalled { NSWorkspace.shared.urlForApplication(withBundleIdentifier: $0) }
|
|
}
|
|
|
|
func isInstalled(applicationURL: (String) -> URL?) -> Bool {
|
|
self == .terminal || applicationURL(self.bundleIdentifier) != nil
|
|
}
|
|
|
|
var appIcon: NSImage? {
|
|
guard let appURL = NSWorkspace.shared.urlForApplication(withBundleIdentifier: self.bundleIdentifier) else {
|
|
return nil
|
|
}
|
|
return NSWorkspace.shared.icon(forFile: appURL.path)
|
|
}
|
|
|
|
var pickerIcon: NSImage? {
|
|
self.appIcon.map(Self.pickerIcon(from:))
|
|
}
|
|
|
|
static func pickerIcon(from icon: NSImage) -> NSImage {
|
|
let sourceSize = icon.size
|
|
let targetSize = self.pickerIconSize
|
|
|
|
guard sourceSize.width.isFinite, sourceSize.width > 0,
|
|
sourceSize.height.isFinite, sourceSize.height > 0
|
|
else {
|
|
let empty = NSImage(size: targetSize)
|
|
empty.isTemplate = icon.isTemplate
|
|
return empty
|
|
}
|
|
|
|
// MenuPickerStyle sizes selected images from their intrinsic NSImage dimensions.
|
|
let resized = NSImage(size: targetSize, flipped: false) { _ in
|
|
let scale = min(targetSize.width / sourceSize.width, targetSize.height / sourceSize.height)
|
|
let scaledSize = NSSize(width: sourceSize.width * scale, height: sourceSize.height * scale)
|
|
let drawingRect = NSRect(
|
|
x: (targetSize.width - scaledSize.width) / 2,
|
|
y: (targetSize.height - scaledSize.height) / 2,
|
|
width: scaledSize.width,
|
|
height: scaledSize.height)
|
|
NSGraphicsContext.current?.imageInterpolation = .high
|
|
icon.draw(
|
|
in: drawingRect,
|
|
from: NSRect(origin: .zero, size: sourceSize),
|
|
operation: .copy,
|
|
fraction: 1)
|
|
return true
|
|
}
|
|
resized.isTemplate = icon.isTemplate
|
|
return resized
|
|
}
|
|
|
|
static var installed: [Self] {
|
|
self.installed { NSWorkspace.shared.urlForApplication(withBundleIdentifier: $0) }
|
|
}
|
|
|
|
static func installed(applicationURL: (String) -> URL?) -> [Self] {
|
|
self.allCases.filter { $0.isInstalled(applicationURL: applicationURL) }
|
|
}
|
|
|
|
static func pickerOptions(selected: Self) -> [Self] {
|
|
self.pickerOptions(selected: selected) { NSWorkspace.shared.urlForApplication(withBundleIdentifier: $0) }
|
|
}
|
|
|
|
static func pickerOptions(selected: Self, applicationURL: (String) -> URL?) -> [Self] {
|
|
self.allCases.filter { $0 == selected || $0.isInstalled(applicationURL: applicationURL) }
|
|
}
|
|
|
|
func appleScript(command: String) -> String {
|
|
let escaped = Self.escapeForAppleScript(command)
|
|
return switch self {
|
|
case .terminal:
|
|
"""
|
|
tell application "Terminal"
|
|
activate
|
|
do script "\(escaped)"
|
|
end tell
|
|
"""
|
|
case .iTerm:
|
|
"""
|
|
tell application "iTerm"
|
|
activate
|
|
set newWindow to (create window with default profile)
|
|
tell current session of newWindow
|
|
write text "\(escaped)"
|
|
end tell
|
|
end tell
|
|
"""
|
|
}
|
|
}
|
|
|
|
static func escapeForAppleScript(_ command: String) -> String {
|
|
command
|
|
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
.replacingOccurrences(of: "\"", with: "\\\"")
|
|
}
|
|
}
|