mirror of
https://github.com/trycua/computer.git
synced 2026-02-18 04:19:38 -06:00
Add local network support for VNC
This commit is contained in:
@@ -25,6 +25,7 @@ struct VMConfig: Codable {
|
||||
private var _display: VMDisplayResolution
|
||||
private var _hardwareModel: Data?
|
||||
private var _machineIdentifier: Data?
|
||||
private var _vncPort: Int?
|
||||
|
||||
// MARK: - Initialization
|
||||
init(
|
||||
@@ -35,7 +36,8 @@ struct VMConfig: Codable {
|
||||
macAddress: String? = nil,
|
||||
display: String,
|
||||
hardwareModel: Data? = nil,
|
||||
machineIdentifier: Data? = nil
|
||||
machineIdentifier: Data? = nil,
|
||||
vncPort: Int? = nil
|
||||
) throws {
|
||||
self.os = os
|
||||
self._cpuCount = cpuCount
|
||||
@@ -45,6 +47,7 @@ struct VMConfig: Codable {
|
||||
self._display = VMDisplayResolution(string: display) ?? VMDisplayResolution(string: "1024x768")!
|
||||
self._hardwareModel = hardwareModel
|
||||
self._machineIdentifier = machineIdentifier
|
||||
self._vncPort = vncPort
|
||||
}
|
||||
|
||||
var display: VMDisplayResolution {
|
||||
@@ -81,6 +84,11 @@ struct VMConfig: Codable {
|
||||
get { _macAddress }
|
||||
set { _macAddress = newValue }
|
||||
}
|
||||
|
||||
var vncPort: Int? {
|
||||
get { _vncPort }
|
||||
set { _vncPort = newValue }
|
||||
}
|
||||
|
||||
mutating func setCpuCount(_ count: Int) {
|
||||
_cpuCount = count
|
||||
@@ -110,6 +118,10 @@ struct VMConfig: Codable {
|
||||
self._display = newDisplay
|
||||
}
|
||||
|
||||
mutating func setVNCPort(_ port: Int) {
|
||||
_vncPort = port
|
||||
}
|
||||
|
||||
// MARK: - Codable
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case _cpuCount = "cpuCount"
|
||||
@@ -120,6 +132,7 @@ struct VMConfig: Codable {
|
||||
case _hardwareModel = "hardwareModel"
|
||||
case _machineIdentifier = "machineIdentifier"
|
||||
case os
|
||||
case _vncPort = "vncPort"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
@@ -133,6 +146,7 @@ struct VMConfig: Codable {
|
||||
_display = VMDisplayResolution(string: try container.decode(String.self, forKey: .display))!
|
||||
_hardwareModel = try container.decodeIfPresent(Data.self, forKey: ._hardwareModel)
|
||||
_machineIdentifier = try container.decodeIfPresent(Data.self, forKey: ._machineIdentifier)
|
||||
_vncPort = try container.decodeIfPresent(Int.self, forKey: ._vncPort)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
@@ -146,5 +160,6 @@ struct VMConfig: Codable {
|
||||
try container.encode(display.string, forKey: .display)
|
||||
try container.encodeIfPresent(_hardwareModel, forKey: ._hardwareModel)
|
||||
try container.encodeIfPresent(_machineIdentifier, forKey: ._machineIdentifier)
|
||||
try container.encodeIfPresent(_vncPort, forKey: ._vncPort)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,9 @@ class VM {
|
||||
throw VMError.internalError("Virtualization service not initialized")
|
||||
}
|
||||
|
||||
try await vncService.start(port: 0, virtualMachine: service.getVirtualMachine())
|
||||
// Use configured port or default to 0 (auto-assign)
|
||||
let port = vmDirContext.config.vncPort ?? 0
|
||||
try await vncService.start(port: port, virtualMachine: service.getVirtualMachine())
|
||||
|
||||
guard let url = vncService.url else {
|
||||
throw VMError.vncNotConfigured
|
||||
@@ -399,4 +401,13 @@ class VM {
|
||||
func finalize(to name: String, home: Home) throws {
|
||||
try vmDirContext.finalize(to: name)
|
||||
}
|
||||
|
||||
// Add method to set VNC port
|
||||
func setVNCPort(_ port: Int) throws {
|
||||
guard !isRunning else {
|
||||
throw VMError.alreadyRunning(vmDirContext.name)
|
||||
}
|
||||
vmDirContext.config.setVNCPort(port)
|
||||
try vmDirContext.saveConfig()
|
||||
}
|
||||
}
|
||||
@@ -30,8 +30,11 @@ final class DefaultVNCService: VNCService {
|
||||
func start(port: Int, virtualMachine: Any?) async throws {
|
||||
let password = Array(PassphraseGenerator().prefix(4)).joined(separator: "-")
|
||||
let securityConfiguration = Dynamic._VZVNCAuthenticationSecurityConfiguration(password: password)
|
||||
|
||||
// Create VNC server with specified port
|
||||
let server = Dynamic._VZVNCServer(port: port, queue: DispatchQueue.main,
|
||||
securityConfiguration: securityConfiguration)
|
||||
|
||||
if let vm = virtualMachine as? VZVirtualMachine {
|
||||
server.virtualMachine = vm
|
||||
}
|
||||
@@ -39,15 +42,21 @@ final class DefaultVNCService: VNCService {
|
||||
|
||||
vncServer = server
|
||||
|
||||
// Wait for port to be assigned
|
||||
while true {
|
||||
if let port: UInt16 = server.port.asUInt16, port != 0 {
|
||||
let url = "vnc://:\(password)@127.0.0.1:\(port)"
|
||||
// Wait for port to be assigned if using port 0 (auto-assign)
|
||||
while port == 0 {
|
||||
if let assignedPort: UInt16 = server.port.asUInt16, assignedPort != 0 {
|
||||
// Get the local IP address for the URL - prefer IPv4
|
||||
let hostIP = try getLocalIPAddress() ?? "127.0.0.1"
|
||||
let url = "vnc://:\(password)@127.0.0.1:\(assignedPort)" // Use localhost for local connections
|
||||
let externalUrl = "vnc://:\(password)@\(hostIP):\(assignedPort)" // External URL for remote connections
|
||||
|
||||
// Save session information
|
||||
let session = VNCSession(
|
||||
url: url
|
||||
)
|
||||
Logger.info("VNC server started", metadata: [
|
||||
"local": url,
|
||||
"external": externalUrl
|
||||
])
|
||||
|
||||
// Save session information with local URL for the client
|
||||
let session = VNCSession(url: url)
|
||||
try vmDirectory.saveSession(session)
|
||||
break
|
||||
}
|
||||
@@ -55,6 +64,44 @@ final class DefaultVNCService: VNCService {
|
||||
}
|
||||
}
|
||||
|
||||
// Modified to prefer IPv4 addresses
|
||||
private func getLocalIPAddress() throws -> String? {
|
||||
var address: String?
|
||||
|
||||
var ifaddr: UnsafeMutablePointer<ifaddrs>?
|
||||
guard getifaddrs(&ifaddr) == 0 else {
|
||||
return nil
|
||||
}
|
||||
defer { freeifaddrs(ifaddr) }
|
||||
|
||||
var ptr = ifaddr
|
||||
while ptr != nil {
|
||||
defer { ptr = ptr?.pointee.ifa_next }
|
||||
|
||||
let interface = ptr?.pointee
|
||||
let family = interface?.ifa_addr.pointee.sa_family
|
||||
|
||||
// Only look for IPv4 addresses
|
||||
if family == UInt8(AF_INET) {
|
||||
let name = String(cString: (interface?.ifa_name)!)
|
||||
if name == "en0" { // Primary interface
|
||||
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
|
||||
getnameinfo(interface?.ifa_addr,
|
||||
socklen_t((interface?.ifa_addr.pointee.sa_len)!),
|
||||
&hostname,
|
||||
socklen_t(hostname.count),
|
||||
nil,
|
||||
0,
|
||||
NI_NUMERICHOST)
|
||||
address = String(cString: hostname, encoding: .utf8)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return address
|
||||
}
|
||||
|
||||
func stop() {
|
||||
if let server = vncServer as? Dynamic {
|
||||
server.stop()
|
||||
|
||||
Reference in New Issue
Block a user