Changes
4 changed files (+126/-113)
-
-
@@ -340,7 +340,7 @@/** * Non NULL-able. */ plac_app_server_selector const * const server_selector; plac_app_server_selector *server_selector; /** * Describes "server"'s current state.
-
@@ -353,7 +353,7 @@/** * NULL by default. Call "plac_app_connect" to fill-in. */ plac_app_server const * const server; plac_app_server *server; } plac_app; /**
-
-
macos/plac/ContentView.swift (deleted)
-
@@ -1,37 +0,0 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 import PlacCore import SwiftUI func getDefaultReceiveWindow() -> UInt32 { var opts = PlacCore.plac_server_scan_options() PlacCore.plac_server_scan_options_init(&opts) return opts.receive_window_ms } struct ContentView: View { var body: some View { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Window = \(getDefaultReceiveWindow())") } .padding() } }
-
-
-
@@ -16,114 +16,143 @@ // SPDX-License-Identifier: Apache-2.0import PlacCore import SwiftUI import os struct FoundServer { let id: String let name: String let version: String let ptr: UnsafeMutablePointer<plac_app_server_selector_entry> } enum DiscoveryResult { case loading case loaded([UnsafeMutablePointer<PlacCore.plac_server>]) case failed(PlacCore.plac_scan_result_code) case nilscanner case loaded([FoundServer]) case failed(plac_app_server_selector_state) case null_pointer } // https://oleb.net/blog/2015/06/c-callbacks-in-swift/ class Context<T> { private(set) var item: T init(_ item: T) { self.item = item } func mutate(_ mutation: (inout T) -> Void) { mutation(&item) } } struct ServerDiscoveryScene: Scene { @Environment(\.scenePhase) private var scenePhase @State private var discovery: DiscoveryResult = .loading private var app: UnsafeMutablePointer<plac_app> init(_ app: UnsafeMutablePointer<plac_app>) { self.app = app } var body: some Scene { WindowGroup { VStack { switch discovery { switch self.discovery { case .loading: Text("Scanning Roon Server on network...") case .loaded(let serverPtrs): case .loaded(let servers): List { ForEach(serverPtrs, id: \.pointee.id) { ptr in Text(String.init(cString: ptr.pointee.name)) ForEach(servers, id: \.id) { server in Text(server.name) } } case .failed(let plac_scan_result_code): switch plac_scan_result_code { case PlacCore.PLAC_SCAN_NETWORK_UNAVAILABLE: Text("Network unavailable") case PlacCore.PLAC_SCAN_UNKNOWN_ERROR: Text("Unknown error") case PlacCore.PLAC_SCAN_SOCKET_PERMISSION_ERROR: Text("Failed to open socket (permission error)") case PlacCore.PLAC_SCAN_SOCKET_SETUP_ERROR: Text("Failed to open socket") case PlacCore.PLAC_SCAN_INVALID_RECEIVE_WINDOW: Text("Invalid receiving window duration") case PlacCore.PLAC_SCAN_OK: Text("OK (unreachable)") case PlacCore.PLAC_SCAN_OUT_OF_MEMORY: case PLAC_APP_SERVER_SELECTOR_ERR_SOCKET: Text("Unable to operate on network socket") case PLAC_APP_SERVER_SELECTOR_ERR_UNEXPECTED: Text("Unexpected error") case PLAC_APP_SERVER_SELECTOR_ERR_THREAD_SPAWN: Text("Unable to spawn worker thread") case PLAC_APP_SERVER_SELECTOR_ERR_OUT_OF_MEMORY: Text("Out of memory") case PlacCore.PLAC_SCAN_UDP_RECV_ERROR: Text("Unabled to receive UDP message") case PlacCore.PLAC_SCAN_UDP_SEND_ERROR: Text("Unable to send UDP message") case PlacCore.PLAC_SCAN_TOO_MANY_SOCKET_ERROR: Text("Failed to open socket (too many socket)") case PlacCore.PLAC_SCAN_NULL_POINTER_ARGS: Text("Failed to setup scanner or options") case PLAC_APP_SERVER_SELECTOR_ERR_SOCKET_PERMISSION: Text( "Permission rejected for network socket operation" ) case PLAC_APP_SERVER_SELECTOR_ERR_NETWORK_UNAVAILABLE: Text("Network unavailable") default: Text("Unexpected error (platform misbehaving)") } case .nilscanner: Text("Failed to scan") case .null_pointer: Text("Out of memory") } } } .onChange(of: scenePhase, initial: true) { if scenePhase == .active { let thread = Thread { switch discovery { case .loaded(let prevPointers): for pointer in prevPointers { PlacCore.plac_server_free(pointer) } default: break } if self.scenePhase == .active { let context = Unmanaged.passRetained(Context(self)) plac_app_server_selector_on_change( app.pointee.server_selector, { ptr in DispatchQueue.main.async { let logger = Logger() guard let ptr = ptr else { logger.error( "Exiting server selector callback because context is NULL" ) return } discovery = .loading let ctx: Context<ServerDiscoveryScene> = Unmanaged.fromOpaque(ptr) .takeUnretainedValue() var opts = PlacCore.plac_server_scan_options() PlacCore.plac_server_scan_options_init(&opts) ctx.mutate { this in switch this.app.pointee.server_selector.pointee.state { case PLAC_APP_SERVER_SELECTOR_LOADING, PLAC_APP_SERVER_SELECTOR_REFRESHING, PLAC_APP_SERVER_SELECTOR_NOT_LOADED: this.discovery = .loading let scanner = PlacCore.plac_server_scanner_make() case PLAC_APP_SERVER_SELECTOR_LOADED: var servers: [FoundServer] = [] var current = this.app.pointee.server_selector!.pointee .entries! if let result = PlacCore.plac_server_scanner_scan( scanner, &opts) { var servers: [UnsafeMutablePointer<PlacCore.plac_server>] = [] for _ in 0...this.app.pointee.server_selector.pointee.entries_len { if let server = current.pointee { servers.append( FoundServer( id: String(cString: server.pointee.id), name: String(cString: server.pointee.name), version: String(cString: server.pointee.version), ptr: server ) ) } current = current.successor() } while true { if let next = PlacCore.plac_scan_result_next( result) { servers.append(next) } else { break this.discovery = .loaded(servers) default: this.discovery = .failed( this.app.pointee.server_selector.pointee.state ) } } } }, context.toOpaque() ) if result.pointee.code == PlacCore.PLAC_SCAN_OK { discovery = .loaded(servers) } else { discovery = .failed(result.pointee.code) } PlacCore.plac_scan_result_free(result) } else { discovery = .nilscanner } PlacCore.plac_server_scanner_free(scanner) } thread.start() plac_app_server_selector_load(self.app.pointee.server_selector) } } }
-
-
-
@@ -14,11 +14,32 @@ // limitations under the License.// // SPDX-License-Identifier: Apache-2.0 import PlacCore import SwiftUI @main struct placApp: App { private var core: Core = Core() var body: some Scene { ServerDiscoveryScene() ServerDiscoveryScene(self.core.app) } } class Core { var app: UnsafeMutablePointer<plac_app> init() { if let app = plac_app_new() { self.app = app } else { fatalError( "Failed to start Plac core library (returned NULL handle, possibly out of memory)" ) } } deinit { plac_app_destroy(self.app) } }
-