diff --git a/sessionbroker/sessionbroker/main.swift b/sessionbroker/sessionbroker/main.swift index 02aca9d..bcee293 100644 --- a/sessionbroker/sessionbroker/main.swift +++ b/sessionbroker/sessionbroker/main.swift @@ -16,11 +16,13 @@ func main() { DispatchQueue.global().async { logger.info("Starting server: sesman") - sesman.start() + // FIXME: wrap using do-catch + try? sesman.start() } logger.info("Starting server: broker") - broker.start() + // FIXME: wrap using do-catch + try? broker.start() } main() \ No newline at end of file diff --git a/sessionprojector/sessionprojector.xcodeproj/project.pbxproj b/sessionprojector/sessionprojector.xcodeproj/project.pbxproj index 13f38a2..89feb2f 100644 --- a/sessionprojector/sessionprojector.xcodeproj/project.pbxproj +++ b/sessionprojector/sessionprojector.xcodeproj/project.pbxproj @@ -407,9 +407,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = sessionprojector/sessionprojector.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XHA76UVA95; FRAMEWORK_SEARCH_PATHS = "$(inherit)/**"; GCC_INPUT_FILETYPE = automatic; GENERATE_INFOPLIST_FILE = YES; @@ -557,9 +559,11 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = sessionprojector/sessionprojector.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = XHA76UVA95; FRAMEWORK_SEARCH_PATHS = "$(inherit)/**"; GCC_INPUT_FILETYPE = automatic; GENERATE_INFOPLIST_FILE = YES; diff --git a/sessionprojector/sessionprojector/AppDelegate.swift b/sessionprojector/sessionprojector/AppDelegate.swift index 4fc322b..a07fa42 100644 --- a/sessionprojector/sessionprojector/AppDelegate.swift +++ b/sessionprojector/sessionprojector/AppDelegate.swift @@ -36,7 +36,7 @@ struct SessionProjectorApp: App { Divider() - Button("\(appState.connections) connection(s)") { + Button("\(appState.connections.count) connection(s)") { } .disabled(true) @@ -240,7 +240,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { try await startScreenRecorder() try sesmanClient.start() - projectionServer.start() + try projectionServer.start() } catch { print(error.localizedDescription) @@ -309,27 +309,31 @@ extension AppDelegate: ProjectionServerDelegate { screenRecorder?.subscribeUpdate(session) session.eventInjector = eventInjector - AppState.instance.connections += 1 + AppState.instance.connections.insert(session) + let clientInfo = session.clientInfo + postUserNotificationSync( title: "New connection", - message: (AppState.instance.connections > 1) ? - "A new connection has been established from (TODO: remote ip address).\nThe content of this session being shared with \(AppState.instance.connections) remote clients." : - "A new connection has been established from (TODO: remote ip address).\nThe content of this session will be shared with the remote client." + message: (AppState.instance.connections.count > 1) ? + "A new connection has been established from \(clientInfo.clientAddress).\nThe content of this session being shared with \(AppState.instance.connections.count) remote clients." : + "A new connection has been established from \(clientInfo.clientAddress).\nThe content of this session will be shared with the remote client." ) } func projectionServer(sessionClosed session: ProjectionSession, id: UInt64) { + let clientInfo = session.clientInfo + screenRecorder?.unsubscribeUpdate(session) session.eventInjector = nil - AppState.instance.connections -= 1 + AppState.instance.connections.remove(session) postUserNotificationSync( title: "Connection closed", - message: (AppState.instance.connections > 0) ? - "Connection has been closed from (TODO: remote ip address).\n\(AppState.instance.connections) remote clients are still connected." : - "Connection has been closed from (TODO: remote ip address)." + message: (AppState.instance.connections.count > 0) ? + "Connection has been closed from \(clientInfo.clientAddress).\n\(AppState.instance.connections.count) remote clients are still connected." : + "Connection has been closed from \(clientInfo.clientAddress)." ) } } diff --git a/sessionprojector/sessionprojector/AppState.swift b/sessionprojector/sessionprojector/AppState.swift index 7de70cf..5d0749f 100644 --- a/sessionprojector/sessionprojector/AppState.swift +++ b/sessionprojector/sessionprojector/AppState.swift @@ -8,6 +8,8 @@ import Foundation import Cocoa +import UlalacaCore + enum ScreenRecorderType: String, Identifiable, CaseIterable, Codable { var id: Self { return self @@ -145,11 +147,11 @@ class AppState: NSObject, ObservableObject { var userPreferences: UserPreferences @Published - var connections: Int = 0 + var connections: Set = [] @Published var isScreenLocked: Bool = false - + var preferencesWindow: NSWindow { get { @@ -159,7 +161,6 @@ class AppState: NSObject, ObservableObject { var aboutAppWindow: AboutAppWindow? = nil - override init() { let fileManager = FileManager.default let userPreferencesPath = fileManager.homeDirectoryForCurrentUser.appendingPathComponent(".sessionprojector.json") diff --git a/sessionprojector/sessionprojector/ipc/ProjectionServer.swift b/sessionprojector/sessionprojector/ipc/ProjectionServer.swift index 85637dc..add3457 100644 --- a/sessionprojector/sessionprojector/ipc/ProjectionServer.swift +++ b/sessionprojector/sessionprojector/ipc/ProjectionServer.swift @@ -35,26 +35,41 @@ class ProjectionServer { .path } - func start() { - serverSocket.bind() - serverSocket.listen() + func start() throws { + try ObjC.evaluate { + serverSocket.bind() + serverSocket.listen() + } while (isServerRunning) { - guard let clientSocket = serverSocket.accept() else { - continue - } - let session = ProjectionSession.init(clientSocket) - sessions.append(session) + do { + try ObjC.evaluate { + guard let clientSocket = serverSocket.accept() else { + return + } + let session = ProjectionSession.init(clientSocket) + sessions.append(session) - delegate?.projectionServer(sessionInitiated: session, id: 0) + if (!session.readHello()) { + session.stopSession() + return + } - session.startSession(errorHandler: { error in - if let index = self.sessions.index(where: { $0.socket.descriptor() == session.socket.descriptor() }) { - self.sessions.remove(at: index) - } + delegate?.projectionServer(sessionInitiated: session, id: 0) - self.delegate?.projectionServer(sessionClosed: session, id: 0) - }) + session.startSession(errorHandler: { error in + if let index = self.sessions.index(where: { $0.socket.descriptor() == session.socket.descriptor() }) { + self.sessions.remove(at: index) + } + + self.delegate?.projectionServer(sessionClosed: session, id: 0) + }) + } + } catch let error as NestedNSExceptionError { + // TODO: handle error + logger.error(error.localizedDescription) + continue + } } } diff --git a/sessionprojector/sessionprojector/ipc/ProjectionSession.swift b/sessionprojector/sessionprojector/ipc/ProjectionSession.swift index c692f38..0f9f9d2 100644 --- a/sessionprojector/sessionprojector/ipc/ProjectionSession.swift +++ b/sessionprojector/sessionprojector/ipc/ProjectionSession.swift @@ -13,9 +13,61 @@ enum ProjectionSessionError: Error { case socketReadError } +enum ClientCodec: UInt8 { + case none = 0 + case rfx = 1 + case h264 = 2 + case nsCodec = 3 +} + + +struct ClientInfo: Hashable { + public static let CLIENT_ID_UNKNOWN = UUID(uuidString: "00000000-0000-0000-0000-000000000001")! + + var id: UUID + + var xrdpUlalacaVersion: String + var clientAddress: String + var clientDescription: String + var clientOSMajor: Int + var clientOSMinor: Int + + var program: String + + var codec: ClientCodec + + var flags: Int + static func from(ipc message: ULIPCProjectionHello) -> ClientInfo { + return ClientInfo( + id: UUID(), + xrdpUlalacaVersion: withUnsafePointer(to: message.xrdpUlalacaVersion) { String(fromUnsafeCStr: $0, length: 32) }, + clientAddress: withUnsafePointer(to: message.clientAddress) { String(fromUnsafeCStr: $0, length: 46) }, + clientDescription: withUnsafePointer(to: message.clientDescription) { String(fromUnsafeCStr: $0, length: 256) }, + clientOSMajor: Int(message.clientOSMajor), + clientOSMinor: Int(message.clientOSMinor), + program: withUnsafePointer(to: message.program) { String(fromUnsafeCStr: $0, length: 512) }, + codec: ClientCodec(rawValue: message.codec) ?? .none, + flags: Int(message.flags) + ) + } + + static func unknown() -> ClientInfo { + return ClientInfo( + id: CLIENT_ID_UNKNOWN, + xrdpUlalacaVersion: "Unknown", + clientAddress: "Unknown", + clientDescription: "Unknown", + clientOSMajor: 0, + clientOSMinor: 0, + program: "Unknown", + codec: .none, + flags: 0 + ) + } +} -class ProjectionSession { +class ProjectionSession: Identifiable { private let logger: ULLogger public let socket: MMUnixSocketConnection @@ -26,6 +78,10 @@ class ProjectionSession { public let mainDisplayId = CGMainDisplayID() public let serialQueue = DispatchQueue(label: "ProjectionSession") + public var id: UUID + + private(set) public var clientInfo: ClientInfo = ClientInfo.unknown() + private(set) public var isSessionRunning: Bool = true private(set) public var messageId: UInt64 = 1; @@ -35,6 +91,7 @@ class ProjectionSession { private(set) public var mainViewport: ViewportInfo? init(_ socket: MMUnixSocketConnection) { + self.id = UUID() self.socket = socket self.logger = createLogger("ProjectionSession (fd \(self.socket.descriptor()))") @@ -90,7 +147,9 @@ class ProjectionSession { try socket.readCStruct(ULIPCProjectionStop.self) suppressOutput = true break - + case TYPE_PROJECTION_HELLO: + // ??? + break case TYPE_PROJECTION_SET_VIEWPORT: self.setViewport(with: try socket.readCStruct(ULIPCProjectionSetViewport.self)) break @@ -111,6 +170,25 @@ class ProjectionSession { mainViewport = ViewportInfo(width: message.width, height: message.height) } + public func readHello(timeout: Double = 0.5) -> Bool { + logger.debug("ProjectionSession::readHello(): timeout is not implemented yet") + + guard let header = try? socket.readCStruct(ULIPCHeader.self) else { + return false + } + + if (header.messageType != TYPE_PROJECTION_HELLO) { + return false + } + + guard let message = try? socket.readCStruct(ULIPCProjectionHello.self) else { + return false + } + self.clientInfo = ClientInfo.from(ipc: message) + + return true + } + private func writeMessage(_ message: T, type: UInt16) { let messageLength = MemoryLayout.size(ofValue: message) @@ -134,6 +212,17 @@ class ProjectionSession { } +extension ProjectionSession: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(socket.descriptor()) + } + + public static func ==(lhs: ProjectionSession, rhs: ProjectionSession) -> Bool { + return lhs.hashValue == rhs.hashValue + } +} + extension ProjectionSession: ScreenUpdateSubscriber { var identifier: Int { get { Int(socket.descriptor()) } diff --git a/sessionprojector/sessionprojector/ui/AboutAppWindowView.swift b/sessionprojector/sessionprojector/ui/AboutAppWindowView.swift index 0b003c7..8f2f3bb 100644 --- a/sessionprojector/sessionprojector/ui/AboutAppWindowView.swift +++ b/sessionprojector/sessionprojector/ui/AboutAppWindowView.swift @@ -7,6 +7,8 @@ import SwiftUI +import UlalacaCore + struct AboutAppWindowView: View { @Environment(\.openURL) @@ -31,9 +33,9 @@ struct AboutAppWindowView: View { .font(.system(size: 36, weight: .ultraLight, design: .default)) } VStack(alignment: .center) { - Text("SessionProjector.app") + Text("sessionprojector.app") .bold() - Text("Development Build") + Text("Version \(UlalacaVersion())") } } } diff --git a/sessionprojector/sessionprojector/ui/PreferencesWindowView.swift b/sessionprojector/sessionprojector/ui/PreferencesWindowView.swift index 1eb3f4b..3b39c70 100644 --- a/sessionprojector/sessionprojector/ui/PreferencesWindowView.swift +++ b/sessionprojector/sessionprojector/ui/PreferencesWindowView.swift @@ -7,6 +7,18 @@ import SwiftUI +struct ConnectionRow: View { + var connection: ProjectionSession + + var body: some View { + VStack() { + Text("\(connection.clientInfo.clientAddress)") + .bold() + Text("ulalaca-xrdp version \(connection.clientInfo.xrdpUlalacaVersion)") + } + } +} + struct PreferencesWindowView: View { @EnvironmentObject var appState: AppState @@ -89,9 +101,9 @@ struct PreferencesWindowView: View { .frame(maxWidth: .infinity) */ HStack { - GroupBox(label: Text("Current Sessions")) { - List { - + GroupBox(label: Text("Clients")) { + List(Array(appState.connections)) { connection in + ConnectionRow(connection: connection) } } .frame(maxWidth: .infinity) diff --git a/ulalacacore/UlalacaCore.xcodeproj/project.pbxproj b/ulalacacore/UlalacaCore.xcodeproj/project.pbxproj index 0837b22..90afaf2 100644 --- a/ulalacacore/UlalacaCore.xcodeproj/project.pbxproj +++ b/ulalacacore/UlalacaCore.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1BA416740564BFE7419AA0BE /* MockedMMUnixSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BA413A8AE9924761E9AD28A /* MockedMMUnixSocket.swift */; }; + 1BA41A6432BFFD572E42A5E5 /* version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BA411653F912FCB3D19BBA6 /* version.swift */; }; 8572115C283B619500C36D5F /* UlalacaCore-Bridging-Header.h in Headers */ = {isa = PBXBuildFile; fileRef = 8572115B283B619500C36D5F /* UlalacaCore-Bridging-Header.h */; }; 85D4F9A5283296C200BF3AEB /* UlalacaCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85D4F99D283296C100BF3AEB /* UlalacaCore.framework */; }; 85D4F9AA283296C200BF3AEB /* socketlibTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 85D4F9A9283296C200BF3AEB /* socketlibTests.m */; }; @@ -46,6 +47,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 1BA411653F912FCB3D19BBA6 /* version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = version.swift; sourceTree = ""; }; 1BA413A8AE9924761E9AD28A /* MockedMMUnixSocket.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockedMMUnixSocket.swift; sourceTree = ""; }; 8572115B283B619500C36D5F /* UlalacaCore-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UlalacaCore-Bridging-Header.h"; sourceTree = ""; }; 85D4F99D283296C100BF3AEB /* UlalacaCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UlalacaCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -100,6 +102,7 @@ 8572115B283B619500C36D5F /* UlalacaCore-Bridging-Header.h */, F4EBDF173966619C72B6FD66 /* ipc */, F4EBDCB7672034F220922124 /* utils */, + 1BA411653F912FCB3D19BBA6 /* version.swift */, ); path = UlalacaCore; sourceTree = ""; @@ -315,6 +318,7 @@ F4EBDAF7ADB65DBA6CDA67B2 /* IPCServerBase.swift in Sources */, F4EBD26C1C7D00F3580CA83E /* IPCClientBase.swift in Sources */, F4EBD5C6CE9FB172E0123C0F /* SessionUtils.swift in Sources */, + 1BA41A6432BFFD572E42A5E5 /* version.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ulalacacore/UlalacaCore/ipc/IPCServerBase.swift b/ulalacacore/UlalacaCore/ipc/IPCServerBase.swift index 30ca93b..7207ddd 100644 --- a/ulalacacore/UlalacaCore/ipc/IPCServerBase.swift +++ b/ulalacacore/UlalacaCore/ipc/IPCServerBase.swift @@ -30,26 +30,37 @@ open class IPCServerBase { self.socketPath = path } - public func start() { - serverSocket.bind() - chmod(socketPath.cString(using: .utf8), S_IRWXU | S_IRWXG | S_IRWXO); + public func start() throws { + try ObjC.evaluate { + serverSocket.bind() + chmod(socketPath.cString(using: .utf8), S_IRWXU | S_IRWXG | S_IRWXO); - serverSocket.listen() - serverRunning = true + serverSocket.listen() + serverRunning = true + } while (serverRunning) { - guard let clientSocket = serverSocket.accept() else { - continue - } + do { + try ObjC.evaluate { + guard let clientSocket = serverSocket.accept() else { + return + } + + let connection = IPCServerConnection(clientSocket) + + Task { + delegate?.connectionEstablished(with: connection) + await clientLoop(connection) + delegate?.connectionClosed(with: connection) + + clientSocket.close() + } + } + } catch let error as NestedNSExceptionError { + // TODO: handle error + print(error.localizedDescription) - let connection = IPCServerConnection(clientSocket) - - Task { - delegate?.connectionEstablished(with: connection) - await clientLoop(connection) - delegate?.connectionClosed(with: connection) - - clientSocket.close() + continue } } } diff --git a/ulalacacore/UlalacaCore/ipc/messages/_global.h b/ulalacacore/UlalacaCore/ipc/messages/_global.h index 270c23c..7f262da 100644 --- a/ulalacacore/UlalacaCore/ipc/messages/_global.h +++ b/ulalacacore/UlalacaCore/ipc/messages/_global.h @@ -3,6 +3,9 @@ #include +#define ULALACA_IPC_PROTO_VERSION 0x1000 +#define ULALACA_IPC_PROTO_VERSION_REVISION 0x0000 + /** * FIXME: naming */ diff --git a/ulalacacore/UlalacaCore/ipc/messages/projector.h b/ulalacacore/UlalacaCore/ipc/messages/projector.h index 0603a89..07e8ebf 100644 --- a/ulalacacore/UlalacaCore/ipc/messages/projector.h +++ b/ulalacacore/UlalacaCore/ipc/messages/projector.h @@ -19,9 +19,14 @@ static const uint16_t TYPE_EVENT_MOUSE_WHEEL = 0x0323; static const uint16_t TYPE_PROJECTION_START = 0x0401; static const uint16_t TYPE_PROJECTION_STOP = 0x0402; +static const uint16_t TYPE_PROJECTION_HELLO = 0x0411; static const uint16_t TYPE_PROJECTION_SET_VIEWPORT = 0x0421; +/* constants: message type (client <-> server) */ +static const uint16_t TYPE_STREAM_DATA = 0x0111; +static const uint16_t TYPE_STREAM_NOTIFY = 0x0112; +static const uint16_t TYPE_STREAM_REQUEST = 0x0113; /* constants: Screen update notification */ static const uint8_t SCREEN_UPDATE_NOTIFY_TYPE_ENTIRE_SCREEN = 0; @@ -53,6 +58,51 @@ static const uint8_t MOUSE_EVENT_BUTTON_LEFT = 0; static const uint8_t MOUSE_EVENT_BUTTON_RIGHT = 1; static const uint8_t MOUSE_EVENT_BUTTON_MIDDLE = 2; +/* constants: Stream-related */ +static const uint8_t STREAM_TYPE_NULL = 0; +static const uint8_t STREAM_TYPE_AUDIO = 1; +static const uint8_t STREAM_TYPE_VIDEO = 2; +static const uint8_t STREAM_TYPE_CLIPBOARD = 3; +/** drag & drop */ +static const uint8_t STREAM_TYPE_FILE = 4; +static const uint8_t STREAM_TYPE_PRINT = 5; + +/** + * indicates the stream will start. + */ +static const uint8_t STREAM_NOTIFY_TYPE_START = 0; +/** + * indicates the stream has ended. + */ +static const uint8_t STREAM_NOTIFY_TYPE_STOP = 1; +/** + * indicates an error occurred during streaming, or the request cannot be fulfilled. + */ +static const uint8_t STREAM_NOTIFY_TYPE_ERROR = 2; +/** + * indicates the client received the data. + */ +static const uint8_t STREAM_NOTIFY_TYPE_ACK = 3; + +static const uint8_t STREAM_DATA_FLAG_NONE = 0b00000000; +/** marks the end of stream. */ +static const uint8_t STREAM_DATA_FLAG_EOS = 0b00000001; +/** + * ignores time-ordered queue. (data will be processed immediately / previous data will be discarded) + */ +static const uint8_t STREAM_DATA_FLAG_URGENT = 0b00000010; + + +/* constants: codec enums for PROJECTION_HELLO */ +static const uint8_t PROJECTION_HELLO_CODEC_NONE = 0; +static const uint8_t PROJECTION_HELLO_CODEC_RFX = 1; +static const uint8_t PROJECTION_HELLO_CODEC_H264 = 2; +static const uint8_t PROJECTION_HELLO_CODEC_NSCODEC = 3; + +/* constants: flags for PROJECTION_HELLO */ +static const uint8_t PROJECTION_HELLO_FLAG_NONE = 0b00000000; + + /* message definition: server -> client */ struct ULIPCScreenUpdateNotify { uint8_t type; @@ -107,6 +157,22 @@ struct ULIPCProjectionStop { uint16_t flags; } MARK_AS_PACKED_STRUCT; +struct ULIPCProjectionHello { + uint8_t xrdpUlalacaVersion[32]; + + uint8_t clientAddress[46]; + uint8_t clientDescription[256]; + + uint32_t clientOSMajor; + uint32_t clientOSMinor; + + uint8_t program[512]; + + uint8_t codec; + + uint16_t flags; +} MARK_AS_PACKED_STRUCT; + struct ULIPCProjectionSetViewport { uint8_t monitorId; uint16_t width; @@ -115,4 +181,45 @@ struct ULIPCProjectionSetViewport { uint16_t flags; } MARK_AS_PACKED_STRUCT; -#endif \ No newline at end of file +struct ULIPCStreamData { + uint8_t resourceType; + uint64_t resourceId; + + uint64_t timestamp; + uint32_t length; + + uint32_t crc; + uint8_t flags; +} MARK_AS_PACKED_STRUCT; + +union ULIPCStreamNotifyData { + /** + * dummy data for padding. + */ + struct { + uint8_t __pad__[32]; + } MARK_AS_PACKED_STRUCT __pad__; + + struct { + uint8_t reason; + } MARK_AS_PACKED_STRUCT error; +} MARK_AS_PACKED_STRUCT; + +struct ULIPCStreamNotify { + uint8_t type; + uint8_t resourceType; + uint64_t resourceId; + + union ULIPCStreamNotifyData data; + + uint16_t flags; +} MARK_AS_PACKED_STRUCT; + +struct ULIPCStreamRequest { + uint8_t resourceType; + uint64_t resourceId; + + uint16_t flags; +} MARK_AS_PACKED_STRUCT; + +#endif diff --git a/ulalacacore/UlalacaCore/version.swift b/ulalacacore/UlalacaCore/version.swift new file mode 100644 index 0000000..b00650c --- /dev/null +++ b/ulalacacore/UlalacaCore/version.swift @@ -0,0 +1,9 @@ +// +// Created by Gyuhwan Park on 2023/06/08. +// + +import Foundation + +public func UlalacaVersion() -> String { + return "0.2.0-alpha1" +} \ No newline at end of file diff --git a/ulalacacore/UlalacaCoreTests/ipc/IPCServerBaseTest.swift b/ulalacacore/UlalacaCoreTests/ipc/IPCServerBaseTest.swift index 030f208..032a43c 100644 --- a/ulalacacore/UlalacaCoreTests/ipc/IPCServerBaseTest.swift +++ b/ulalacacore/UlalacaCoreTests/ipc/IPCServerBaseTest.swift @@ -32,7 +32,11 @@ final class IPCServerBaseTest: XCTestCase { server.delegate = self let serverTask = Task { - server.start() + do { + try server.start() + } catch { + XCTFail() + } } // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. @@ -60,7 +64,11 @@ final class IPCServerBaseTest: XCTestCase { server.delegate = self let serverTask = Task { - server.start() + do { + try server.start() + } catch { + XCTFail() + } } // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results.