refactor: Fix macOS standalone application
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
#if os(macOS)
|
||||
import Foundation
|
||||
import Libbox
|
||||
import os
|
||||
|
||||
private let logger = Logger(category: "CommandXPC")
|
||||
|
||||
@objc public protocol CommandXPCProtocol {
|
||||
func connectToCommandServer(reply: @escaping (FileHandle?, NSError?) -> Void)
|
||||
func registerUserServiceEndpoint(_ endpoint: NSXPCListenerEndpoint?, reply: @escaping (NSError?) -> Void)
|
||||
func extensionRequirements(reply: @escaping (Bool, Bool, NSError?) -> Void)
|
||||
}
|
||||
|
||||
class CommandXPCService: NSObject, NSXPCListenerDelegate {
|
||||
let socketPath: String
|
||||
var commandServer: LibboxCommandServer?
|
||||
|
||||
private let serviceReadyLock = NSLock()
|
||||
private var _serviceReady = false
|
||||
private var serviceReadyContinuations: [CheckedContinuation<Void, Never>] = []
|
||||
|
||||
init(socketPath: String) {
|
||||
self.socketPath = socketPath
|
||||
}
|
||||
|
||||
func waitForServiceReady() async {
|
||||
serviceReadyLock.lock()
|
||||
if _serviceReady {
|
||||
serviceReadyLock.unlock()
|
||||
return
|
||||
}
|
||||
await withCheckedContinuation { continuation in
|
||||
serviceReadyContinuations.append(continuation)
|
||||
serviceReadyLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func markServiceReady() {
|
||||
serviceReadyLock.lock()
|
||||
_serviceReady = true
|
||||
let continuations = serviceReadyContinuations
|
||||
serviceReadyContinuations.removeAll()
|
||||
serviceReadyLock.unlock()
|
||||
for continuation in continuations {
|
||||
continuation.resume()
|
||||
}
|
||||
}
|
||||
|
||||
func markServiceNotReady() {
|
||||
serviceReadyLock.lock()
|
||||
_serviceReady = false
|
||||
serviceReadyLock.unlock()
|
||||
}
|
||||
|
||||
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||
let allowedBundleIDs = [AppConfiguration.packageName + ".standalone"]
|
||||
guard XPCConnectionValidator.validateConnection(
|
||||
newConnection,
|
||||
teamID: AppConfiguration.teamID,
|
||||
allowedBundleIDs: allowedBundleIDs
|
||||
) else {
|
||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
||||
return false
|
||||
}
|
||||
|
||||
let exportedInterface = NSXPCInterface(with: CommandXPCProtocol.self)
|
||||
CommandXPC.configureInterface(exportedInterface)
|
||||
newConnection.exportedInterface = exportedInterface
|
||||
newConnection.exportedObject = CommandXPCHandler(service: self)
|
||||
newConnection.resume()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private class CommandXPCHandler: NSObject, CommandXPCProtocol {
|
||||
private let service: CommandXPCService
|
||||
|
||||
init(service: CommandXPCService) {
|
||||
self.service = service
|
||||
}
|
||||
|
||||
func connectToCommandServer(reply: @escaping (FileHandle?, NSError?) -> Void) {
|
||||
do {
|
||||
let handle = try connectToUnixSocket(path: service.socketPath)
|
||||
reply(handle, nil)
|
||||
} catch {
|
||||
reply(nil, error as NSError)
|
||||
}
|
||||
}
|
||||
|
||||
func registerUserServiceEndpoint(_ endpoint: NSXPCListenerEndpoint?, reply: @escaping (NSError?) -> Void) {
|
||||
if let endpoint {
|
||||
UserServiceEndpointRegistry.shared.update(endpoint)
|
||||
} else {
|
||||
UserServiceEndpointRegistry.shared.clear()
|
||||
}
|
||||
reply(nil)
|
||||
}
|
||||
|
||||
func extensionRequirements(reply: @escaping (Bool, Bool, NSError?) -> Void) {
|
||||
Task {
|
||||
await service.waitForServiceReady()
|
||||
guard let commandServer = service.commandServer else {
|
||||
reply(false, false, NSError(domain: "CommandXPC", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Command server not available",
|
||||
]))
|
||||
return
|
||||
}
|
||||
let needWIFI = commandServer.needWIFIState()
|
||||
let needProcess = commandServer.needFindProcess()
|
||||
reply(needWIFI, needProcess, nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func connectToUnixSocket(path: String) throws -> FileHandle {
|
||||
let fd = socket(AF_UNIX, SOCK_STREAM, 0)
|
||||
guard fd >= 0 else {
|
||||
throw NSError(domain: "CommandXPC", code: Int(errno), userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to create socket: \(String(cString: strerror(errno)))",
|
||||
])
|
||||
}
|
||||
|
||||
var addr = sockaddr_un()
|
||||
addr.sun_family = sa_family_t(AF_UNIX)
|
||||
let pathSize = MemoryLayout.size(ofValue: addr.sun_path)
|
||||
withUnsafeMutableBytes(of: &addr.sun_path) { buffer in
|
||||
_ = path.withCString { cString in
|
||||
strncpy(buffer.baseAddress!.assumingMemoryBound(to: CChar.self), cString, pathSize - 1)
|
||||
}
|
||||
}
|
||||
|
||||
let connectResult = withUnsafePointer(to: &addr) { ptr in
|
||||
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { sockaddrPtr in
|
||||
connect(fd, sockaddrPtr, socklen_t(MemoryLayout<sockaddr_un>.size))
|
||||
}
|
||||
}
|
||||
|
||||
guard connectResult >= 0 else {
|
||||
close(fd)
|
||||
throw NSError(domain: "CommandXPC", code: Int(errno), userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to connect to \(path): \(String(cString: strerror(errno)))",
|
||||
])
|
||||
}
|
||||
|
||||
return FileHandle(fileDescriptor: fd, closeOnDealloc: false)
|
||||
}
|
||||
}
|
||||
|
||||
public class CommandXPCDialer: NSObject, LibboxXPCDialerProtocol {
|
||||
public static let shared = CommandXPCDialer()
|
||||
|
||||
public func dialXPC(_ ret0_: UnsafeMutablePointer<Int32>?) throws {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: Int32 = -1
|
||||
var resultError: Error?
|
||||
|
||||
let machServiceName = AppConfiguration.appGroupID + ".system"
|
||||
let connection = NSXPCConnection(machServiceName: machServiceName)
|
||||
let remoteInterface = NSXPCInterface(with: CommandXPCProtocol.self)
|
||||
CommandXPC.configureInterface(remoteInterface)
|
||||
connection.remoteObjectInterface = remoteInterface
|
||||
connection.resume()
|
||||
|
||||
let proxy = connection.remoteObjectProxyWithErrorHandler { error in
|
||||
logger.error("XPC proxy error: \(error.localizedDescription)")
|
||||
resultError = error
|
||||
semaphore.signal()
|
||||
} as! CommandXPCProtocol
|
||||
|
||||
proxy.connectToCommandServer { handle, error in
|
||||
if let error {
|
||||
logger.error("connectToCommandServer error: \(error.localizedDescription)")
|
||||
resultError = error
|
||||
} else if let handle {
|
||||
result = dup(handle.fileDescriptor)
|
||||
}
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
semaphore.wait()
|
||||
connection.invalidate()
|
||||
|
||||
if let error = resultError {
|
||||
throw error
|
||||
}
|
||||
if result < 0 {
|
||||
logger.error("dialXPC failed: No file handle returned")
|
||||
throw NSError(domain: "CommandXPCDialer", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "No file handle returned",
|
||||
])
|
||||
}
|
||||
ret0_?.pointee = result
|
||||
}
|
||||
}
|
||||
|
||||
public enum CommandXPC {
|
||||
public static func configureInterface(_ interface: NSXPCInterface) {
|
||||
let fileHandleClasses = NSSet(array: [FileHandle.self]) as! Set<AnyHashable>
|
||||
interface.setClasses(
|
||||
fileHandleClasses,
|
||||
for: #selector(CommandXPCProtocol.connectToCommandServer(reply:)),
|
||||
argumentIndex: 0,
|
||||
ofReply: true
|
||||
)
|
||||
let endpointClasses = NSSet(array: [NSXPCListenerEndpoint.self]) as! Set<AnyHashable>
|
||||
interface.setClasses(
|
||||
endpointClasses,
|
||||
for: #selector(CommandXPCProtocol.registerUserServiceEndpoint(_:reply:)),
|
||||
argumentIndex: 0,
|
||||
ofReply: false
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user