mirror of https://github.com/cirruslabs/tart.git
Full-fledged VNC support (#126)
This commit is contained in:
parent
2020324ef5
commit
1e90752ded
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue