101 lines
4.8 KiB
Swift
101 lines
4.8 KiB
Swift
import AppKit
|
|
import Darwin
|
|
import TerebiConfiguration
|
|
|
|
@main
|
|
@MainActor
|
|
enum TerebiCLI {
|
|
static func main() async {
|
|
let command: PlayerCommand
|
|
do {
|
|
command = try PlayerCommand.parse(Array(CommandLine.arguments.dropFirst()))
|
|
} catch {
|
|
fail(error.localizedDescription, code: 64)
|
|
}
|
|
switch command {
|
|
case .help:
|
|
print(PlayerCommand.usage)
|
|
case .printDefaultConfig:
|
|
print(PlayerConfiguration.defaultTOML, terminator: "")
|
|
case .launch(let url, let newInstance, let noConfig, let progressFile):
|
|
await launch(url: url, sourceFile: nil, newInstance: newInstance, noConfig: noConfig, progressFile: progressFile)
|
|
case .launchSource(let sourceFile, let newInstance, let noConfig, let progressFile):
|
|
await launch(url: nil, sourceFile: sourceFile, newInstance: newInstance, noConfig: noConfig, progressFile: progressFile)
|
|
case .launchQueue(let queueFile, let newInstance, let noConfig):
|
|
await launch(url: nil, sourceFile: nil, newInstance: newInstance, noConfig: noConfig,
|
|
progressFile: nil, queueFile: queueFile)
|
|
}
|
|
}
|
|
|
|
private static func launch(url: URL?, sourceFile: URL?, newInstance: Bool, noConfig: Bool,
|
|
progressFile: URL?, queueFile: URL? = nil) async {
|
|
if let progressFile {
|
|
var existing = stat()
|
|
guard lstat(progressFile.path, &existing) != 0 else {
|
|
fail("Progress file already exists: \(progressFile.path). Supply a nonexistent destination.", code: 73)
|
|
}
|
|
guard errno == ENOENT else {
|
|
fail("Could not inspect progress file \(progressFile.path): \(String(cString: strerror(errno))).", code: 73)
|
|
}
|
|
}
|
|
let configURL = PlayerConfiguration.fileURL(environment: ProcessInfo.processInfo.environment,
|
|
homeDirectory: FileManager.default.homeDirectoryForCurrentUser)
|
|
if !noConfig {
|
|
do {
|
|
_ = try PlayerConfiguration.load(from: configURL)
|
|
} catch {
|
|
fail(error.localizedDescription, code: 78)
|
|
}
|
|
}
|
|
var size: UInt32 = 0
|
|
_ = _NSGetExecutablePath(nil, &size)
|
|
var path = [CChar](repeating: 0, count: Int(size))
|
|
guard _NSGetExecutablePath(&path, &size) == 0 else {
|
|
fail("Could not locate the CLI executable.", code: 69)
|
|
}
|
|
let executable = path.withUnsafeBufferPointer {
|
|
URL(fileURLWithPath: String(cString: $0.baseAddress!)).resolvingSymlinksInPath()
|
|
}
|
|
let macOS = executable.deletingLastPathComponent()
|
|
let contents = macOS.deletingLastPathComponent()
|
|
let app = contents.deletingLastPathComponent()
|
|
guard macOS.lastPathComponent == "MacOS", contents.lastPathComponent == "Contents",
|
|
app.pathExtension == "app" else {
|
|
fail("The CLI must be installed inside terebi.app. Rebuild and reinstall the bundled app.", code: 69)
|
|
}
|
|
if let error = FFmpegRuntime.error(for: app) {
|
|
fail(error, code: 69)
|
|
}
|
|
let configuration = NSWorkspace.OpenConfiguration()
|
|
configuration.createsNewApplicationInstance = newInstance
|
|
// LaunchServices does not inherit the invoking shell's XDG environment.
|
|
configuration.arguments = noConfig ? ["--no-config"] : ["--config-file", configURL.absoluteString]
|
|
if let progressFile {
|
|
configuration.arguments += ["--progress-file", progressFile.path]
|
|
}
|
|
if let queueFile {
|
|
configuration.arguments += ["--queue-file", queueFile.path]
|
|
}
|
|
if let sourceFile {
|
|
configuration.arguments += ["--source-file", sourceFile.path]
|
|
}
|
|
if newInstance, let url {
|
|
configuration.arguments += ["--open-media", url.absoluteString]
|
|
}
|
|
do {
|
|
// Launch an application, not a document, so SwiftUI creates its singleton window.
|
|
_ = try await NSWorkspace.shared.openApplication(at: app, configuration: configuration)
|
|
if !newInstance, let url {
|
|
_ = try await NSWorkspace.shared.open([url], withApplicationAt: app, configuration: configuration)
|
|
}
|
|
} catch {
|
|
fail("Could not launch the player: \(error.localizedDescription)", code: 69)
|
|
}
|
|
}
|
|
|
|
private static func fail(_ message: String, code: Int32) -> Never {
|
|
FileHandle.standardError.write(Data("terebi: \(message)\n".utf8))
|
|
exit(code)
|
|
}
|
|
}
|