- SessionController wraps RoyalVNCKit.VNCConnection via nonisolated delegate adapter that bridges callbacks to @MainActor; Keychain-resolved passwords; reconnect with jittered exponential backoff; NWPathMonitor adaptive-quality hook; framebuffer rendered to CALayer.contents from didUpdateFramebuffer. - Touch + trackpad input modes with floating soft cursor overlay; hardware keyboard via pressesBegan/Ended → X11 keysyms; UIPointerInteraction with hidden cursor for indirect pointers; pinch-to-zoom; Apple Pencil as direct touch; two-finger pan / indirect scroll wheel events. - Bidirectional clipboard sync (per-connection opt-in); multi-monitor screen picker with input remapping; screenshot capture → share sheet; on-disconnect reconnect/close prompt; view-only and curtain-mode persisted. - iPad multi-window via WindowGroup(for: UUID.self) + context-menu open; CloudKit-backed ModelContainer with local fallback; PrivacyInfo.xcprivacy. 10 VNCCore tests + 4 VNCUI tests pass; iPhone and iPad simulator builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
1.9 KiB
Swift
65 lines
1.9 KiB
Swift
import SwiftUI
|
|
import SwiftData
|
|
import VNCCore
|
|
import VNCUI
|
|
|
|
@main
|
|
struct VNCApp: App {
|
|
@State private var appState = AppStateController()
|
|
|
|
private let sharedContainer: ModelContainer = {
|
|
let schema = Schema([SavedConnection.self])
|
|
let cloudKitConfiguration = ModelConfiguration(
|
|
"CloudConnections",
|
|
schema: schema,
|
|
cloudKitDatabase: .automatic
|
|
)
|
|
if let container = try? ModelContainer(for: schema,
|
|
configurations: [cloudKitConfiguration]) {
|
|
return container
|
|
}
|
|
let local = ModelConfiguration(
|
|
"LocalConnections",
|
|
schema: schema,
|
|
cloudKitDatabase: .none
|
|
)
|
|
return (try? ModelContainer(for: schema, configurations: [local]))
|
|
?? (try! ModelContainer(for: SavedConnection.self))
|
|
}()
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
RootView()
|
|
.environment(appState)
|
|
.task { await appState.initialize() }
|
|
}
|
|
.modelContainer(sharedContainer)
|
|
|
|
WindowGroup("Session", for: UUID.self) { $connectionID in
|
|
DetachedSessionWindow(connectionID: connectionID)
|
|
}
|
|
.modelContainer(sharedContainer)
|
|
}
|
|
}
|
|
|
|
private struct DetachedSessionWindow: View {
|
|
let connectionID: UUID?
|
|
@Environment(\.modelContext) private var modelContext
|
|
@Query private var connections: [SavedConnection]
|
|
|
|
var body: some View {
|
|
if let id = connectionID,
|
|
let match = connections.first(where: { $0.id == id }) {
|
|
NavigationStack {
|
|
SessionView(connection: match)
|
|
}
|
|
} else {
|
|
ContentUnavailableView(
|
|
"Connection unavailable",
|
|
systemImage: "questionmark.app",
|
|
description: Text("This window's connection is no longer available.")
|
|
)
|
|
}
|
|
}
|
|
}
|