Move full-fledged VNC support to --experimental-vnc (#154)

This commit is contained in:
Nikolay Edigaryev 2022-07-21 12:37:43 +03:00 committed by GitHub
parent c1dee4f9b2
commit 48cd4b47e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 30 deletions

View File

@ -24,41 +24,57 @@ struct Run: AsyncParsableCommand {
@Flag(help: ArgumentHelp(
"Use screen sharing instead of the built-in UI.",
discussion: "Useful since VNC supports copy/paste, drag and drop, etc.\nNote that Remote Login option should be enabled inside the VM."))
discussion: "Useful since Screen Sharing supports copy/paste, drag and drop, etc.\n"
+ "Note that Remote Login option should be enabled inside the VM."))
var vnc: Bool = false
@Flag(help: ArgumentHelp(
"Use Virtualization.Framework's VNC server instead of the build-in UI.",
discussion: "Useful since this type of VNC is available in recovery mode and in macOS installation.\n"
+ "Note that this feature is experimental and there may be bugs present when using VNC."))
var vncExperimental: Bool = false
@Flag var withSoftnet: Bool = false
func validate() throws {
if vnc && vncExperimental {
throw ValidationError("--vnc and --vnc-experimental are mutually exclusive")
}
}
@MainActor
func run() async throws {
let vmDir = try VMStorageLocal().open(name)
vm = try VM(vmDir: vmDir, withSoftnet: withSoftnet)
var vncWrapper: VNCWrapper?
if vnc {
vncWrapper = VNCWrapper(virtualMachine: vm!.virtualMachine)
}
let vncImpl: VNC? = try {
if vnc {
let vmConfig = try VMConfig.init(fromURL: vmDir.configURL)
return ScreenSharingVNC(vmConfig: vmConfig)
} else if vncExperimental {
return FullFledgedVNC(virtualMachine: vm!.virtualMachine)
} else {
return nil
}
}()
let task = Task {
do {
if let vncWrapper = vncWrapper {
let port = try await vncWrapper.waitForPort()
let url = URL(string: "vnc://:\(vncWrapper.password)@127.0.0.1:\(port)")!
if let vncImpl = vncImpl {
let vncURL = try await vncImpl.waitForURL()
if noGraphics || ProcessInfo.processInfo.environment["CI"] != nil {
print("VNC server is running at \(url)")
print("VNC server is running at \(vncURL)")
} else {
print("Opening \(url)...")
NSWorkspace.shared.open(url)
print("Opening \(vncURL)...")
NSWorkspace.shared.open(vncURL)
}
}
try await vm!.run(recovery)
if let vncWrapper = vncWrapper {
try vncWrapper.stop()
if let vncImpl = vncImpl {
try vncImpl.stop()
}
Foundation.exit(0)
@ -79,7 +95,7 @@ struct Run: AsyncParsableCommand {
}
sigintSrc.activate()
if noGraphics || vnc {
if noGraphics || vnc || vncExperimental {
dispatchMain()
} else {
runUI()

View File

@ -2,7 +2,7 @@ import Foundation
import Dynamic
import Virtualization
class VNCWrapper {
class FullFledgedVNC: VNC {
let password: String
private let vnc: Dynamic
@ -15,6 +15,19 @@ class VNCWrapper {
vnc.start()
}
func waitForURL() async throws -> URL {
while true {
// Port is 0 shortly after start(),
// but will be initialized later
if let port = vnc.port.asUInt16, port != 0 {
return URL(string: "vnc://:\(password)@127.0.0.1:\(port)")!
}
// Wait 50 ms.
try await Task.sleep(nanoseconds: 50_000_000)
}
}
func stop() throws {
vnc.stop()
}
@ -22,17 +35,4 @@ class VNCWrapper {
deinit {
try? stop()
}
func waitForPort() 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)
}
}
}

View File

@ -0,0 +1,25 @@
import Foundation
import Dynamic
import Virtualization
class ScreenSharingVNC: VNC {
let vmConfig: VMConfig
init(vmConfig: VMConfig) {
self.vmConfig = vmConfig
}
func waitForURL() async throws -> URL {
let ip = try await IP.resolveIP(vmConfig, secondsToWait: 60)
if let ip = ip {
return URL(string: "vnc://\(ip)")!
}
throw IPNotFound()
}
func stop() throws {
// nothing to do
}
}

View File

@ -0,0 +1,6 @@
import Foundation
protocol VNC {
func waitForURL() async throws -> URL
func stop() throws
}