Full-fledged VNC support (#126)

This commit is contained in:
Nikolay Edigaryev 2022-06-20 18:53:17 +03:00 committed by GitHub
parent 2020324ef5
commit 1e90752ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 2126 additions and 10 deletions

View File

@ -37,10 +37,16 @@ struct Run: AsyncParsableCommand {
let vmDir = try VMStorageLocal().open(name)
vm = try VM(vmDir: vmDir)
var vncWrapper: VNCWrapper?
if vnc {
vncWrapper = VNCWrapper(virtualMachine: vm!.virtualMachine)
}
let runTask = Task {
do {
try await vm!.run(recovery)
// wait for VM to be in a final state before exit
while !(vm?.inFinalState ?? false) {
try await Task.sleep(nanoseconds: 1_000_000)
@ -57,25 +63,27 @@ struct Run: AsyncParsableCommand {
Foundation.exit(1)
}
}
if vnc {
if let vncWrapper = vncWrapper {
do {
print("Waiting for the VM to boot...")
let resolvedIP = try await IP.resolveIP(vm!.config, secondsToWait: 60)
guard let ip = resolvedIP else {
throw IPNotFound()
let (port, password) = try await vncWrapper.credentials()
let url = URL(string: "vnc://:\(password)@127.0.0.1:\(port)")!
print("Opening \(url)...")
if ProcessInfo.processInfo.environment["CI"] == nil {
NSWorkspace.shared.open(url)
}
let url = URL(string: "vnc://\(ip)")!
print("Opening \(url)")
NSWorkspace.shared.open(url)
} catch {
print("Failed to get an IP for screen sharing: \(error)")
}
} else if !noGraphics {
runUI()
}
// wait for VM to get into a final state
try await runTask.value
if let vncWrapper = vncWrapper {
try vncWrapper.stop()
}
}
private func runUI() {

View File

@ -0,0 +1,13 @@
import Foundation
struct PassphraseGenerator: Sequence {
func makeIterator() -> PassphraseIterator {
PassphraseIterator()
}
}
struct PassphraseIterator: IteratorProtocol {
mutating func next() -> String? {
passphrases[Int(arc4random_uniform(UInt32(passphrases.count)))]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
import Foundation
import Dynamic
import Virtualization
class VNCWrapper {
private let password: String
private let vnc: Dynamic
init(virtualMachine: VZVirtualMachine) {
password = Array(PassphraseGenerator().prefix(4)).joined(separator: "-")
let securityConfiguration = Dynamic._VZVNCAuthenticationSecurityConfiguration(password: password)
vnc = Dynamic._VZVNCServer(port: 0, queue: DispatchQueue.global(),
securityConfiguration: securityConfiguration)
vnc.virtualMachine = virtualMachine
vnc.start()
}
func credentials() async throws -> (UInt16, String) {
(try await Self.waitForPort(vnc: vnc), password)
}
func stop() throws {
vnc.stop()
}
deinit {
try? stop()
}
private static func waitForPort(vnc: Dynamic) async throws -> UInt16 {
while true {
// Port is 0 shortly after start(),
// but will be initialized later
if let port = vnc.port.asUInt16, port != 0 {
return port
}
// Wait 50 ms.
try await Task.sleep(nanoseconds: 50_000_000)
}
}
}