63 lines
2.1 KiB
Swift
63 lines
2.1 KiB
Swift
import Darwin
|
|
import Foundation
|
|
|
|
enum RelaunchError: LocalizedError {
|
|
case missingBundle
|
|
case failedToLaunch(Int32)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .missingBundle:
|
|
return "The macoc .app bundle is not available."
|
|
case let .failedToLaunch(code):
|
|
return "LaunchServices could not restart macoc (open exit code \(code))."
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Relaunch through LaunchServices, never by directly executing
|
|
/// Contents/MacOS/macoc. Direct binary execution can lose macOS app/TCC
|
|
/// identity and cause fresh Accessibility prompts.
|
|
///
|
|
/// /usr/bin/open only launches the signed app; it is not granted input
|
|
/// permissions and never interacts with the HID remapping.
|
|
enum ApplicationRelauncher {
|
|
static func spawn(executableURL: URL) throws {
|
|
let appURL = executableURL.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
|
|
guard appURL.pathExtension == "app",
|
|
FileManager.default.fileExists(atPath: appURL.path),
|
|
FileManager.default.isExecutableFile(atPath: executableURL.path) else {
|
|
throw RelaunchError.missingBundle
|
|
}
|
|
|
|
let launcher = Process()
|
|
launcher.executableURL = URL(fileURLWithPath: "/usr/bin/open")
|
|
launcher.arguments = [
|
|
"-n", "-a", appURL.path,
|
|
"--args", "--wait-for-pid=\(ProcessInfo.processInfo.processIdentifier)"
|
|
]
|
|
launcher.standardOutput = FileHandle.nullDevice
|
|
launcher.standardError = FileHandle.nullDevice
|
|
try launcher.run()
|
|
launcher.waitUntilExit()
|
|
guard launcher.terminationStatus == 0 else {
|
|
throw RelaunchError.failedToLaunch(launcher.terminationStatus)
|
|
}
|
|
}
|
|
|
|
static func waitForPreviousProcess(_ parentPID: pid_t) -> Bool {
|
|
guard parentPID > 1 else { return false }
|
|
|
|
// New instance must not own the HID mapping until the old one exits.
|
|
for _ in 0..<200 {
|
|
if kill(parentPID, 0) == -1 {
|
|
return errno == ESRCH
|
|
}
|
|
usleep(50_000)
|
|
}
|
|
return false
|
|
}
|
|
}
|