Phases 1-4: full VNC client implementation
- 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>
This commit is contained in:
@@ -1,17 +1,64 @@
|
||||
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(for: SavedConnection.self)
|
||||
.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.")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user