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.")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.1</string>
|
||||
<string>0.4</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>4</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Discover computers on your network that you can control remotely.</string>
|
||||
<key>NSPasteboardUsageDescription</key>
|
||||
<string>Sync the clipboard between this device and the remote computer when you opt in.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Optionally capture a frame of the remote screen.</string>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_rfb._tcp</string>
|
||||
@@ -34,6 +38,8 @@
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<false/>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
@@ -51,5 +57,7 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
39
Screens/Resources/PrivacyInfo.xcprivacy
Normal file
39
Screens/Resources/PrivacyInfo.xcprivacy
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
<key>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user