| 1144 | } |
| 1145 | |
| 1146 | function hostSource() { |
| 1147 | return `import Darwin |
| 1148 | import Foundation |
| 1149 | import Network |
| 1150 | import SwiftUI |
| 1151 | import UIKit |
| 1152 | |
| 1153 | private typealias PreviewFactory = @convention(c) () -> UnsafeMutableRawPointer |
| 1154 | private let simDeckPreviewReloadPortStart: UInt16 = ${RELOAD_PORT_START} |
| 1155 | private let simDeckPreviewReloadPortLimit: UInt16 = ${RELOAD_PORT_LIMIT} |
| 1156 | private let simDeckPreviewReloadPrefix = "${RELOAD_PROTOCOL_PREFIX}" |
| 1157 | private let simDeckPreviewReloadBytesPrefix = "${RELOAD_BYTES_PROTOCOL_PREFIX}" |
| 1158 | |
| 1159 | @MainActor |
| 1160 | final class PreviewStore: ObservableObject { |
| 1161 | @Published var controller: UIViewController? |
| 1162 | @Published var status = "Waiting for SimDeck preview dylib..." |
| 1163 | private var handles: [UnsafeMutableRawPointer] = [] |
| 1164 | private var listener: NWListener? |
| 1165 | |
| 1166 | init() { |
| 1167 | startReloadListener() |
| 1168 | } |
| 1169 | |
| 1170 | @discardableResult |
| 1171 | func load(path rawPath: String) -> Bool { |
| 1172 | let path = rawPath.removingPercentEncoding ?? rawPath |
| 1173 | guard FileManager.default.fileExists(atPath: path) else { |
| 1174 | status = "Missing dylib: \\(path)" |
| 1175 | return false |
| 1176 | } |
| 1177 | guard let handle = dlopen(path, RTLD_NOW | RTLD_LOCAL) else { |
| 1178 | status = String(cString: dlerror()) |
| 1179 | return false |
| 1180 | } |
| 1181 | guard let symbol = dlsym(handle, "simdeck_make_preview_view_controller") else { |
| 1182 | status = "Missing simdeck_make_preview_view_controller" |
| 1183 | return false |
| 1184 | } |
| 1185 | let factory = unsafeBitCast(symbol, to: PreviewFactory.self) |
| 1186 | let pointer = factory() |
| 1187 | let nextController = Unmanaged<UIViewController>.fromOpaque(pointer).takeRetainedValue() |
| 1188 | nextController.view.backgroundColor = .systemBackground |
| 1189 | handles.append(handle) |
| 1190 | controller = nextController |
| 1191 | status = "Loaded \\((path as NSString).lastPathComponent)" |
| 1192 | return true |
| 1193 | } |
| 1194 | |
| 1195 | private func startReloadListener() { |
| 1196 | for offset in 0..<simDeckPreviewReloadPortLimit { |
| 1197 | guard let port = NWEndpoint.Port(rawValue: simDeckPreviewReloadPortStart + offset) else { |
| 1198 | continue |
| 1199 | } |
| 1200 | do { |
| 1201 | let listener = try NWListener(using: .tcp, on: port) |
| 1202 | listener.newConnectionHandler = { [weak self] connection in |
| 1203 | self?.accept(connection) |