|
@@ -108,6 +108,7 @@ extension UTMScriptingConfigImpl {
|
|
|
"drives": config.drives.map({ serializeQemuDriveExisting($0) }),
|
|
|
"networkInterfaces": config.networks.enumerated().map({ serializeQemuNetwork($1, index: $0) }),
|
|
|
"serialPorts": config.serials.enumerated().map({ serializeQemuSerial($1, index: $0) }),
|
|
|
+ "displays": config.displays.map({ serializeQemuDisplay($0)}),
|
|
|
"qemuAdditionalArguments": config.qemu.additionalArguments.map({ serializeQemuAdditionalArgument($0)}),
|
|
|
]
|
|
|
}
|
|
@@ -190,6 +191,24 @@ extension UTMScriptingConfigImpl {
|
|
|
]
|
|
|
}
|
|
|
|
|
|
+ private func qemuScaler(from filter: QEMUScaler) -> UTMScriptingQemuScaler {
|
|
|
+ switch filter {
|
|
|
+ case .linear: return .linear
|
|
|
+ case .nearest: return .nearest
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func serializeQemuDisplay(_ config: UTMQemuConfigurationDisplay) -> [AnyHashable : Any] {
|
|
|
+ [
|
|
|
+ "id": config.id.uuidString,
|
|
|
+ "hardware": config.hardware.rawValue,
|
|
|
+ "dynamicResolution": config.isDynamicResolution,
|
|
|
+ "nativeResolution": config.isNativeResolution,
|
|
|
+ "upscalingFilter": qemuScaler(from: config.upscalingFilter).rawValue,
|
|
|
+ "downscalingFilter": qemuScaler(from: config.downscalingFilter).rawValue,
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
private func serializeQemuAdditionalArgument(_ argument: QEMUArgument) -> [AnyHashable: Any] {
|
|
|
var serializedArgument: [AnyHashable: Any] = [
|
|
|
"argumentString": argument.string
|
|
@@ -213,6 +232,7 @@ extension UTMScriptingConfigImpl {
|
|
|
"drives": config.drives.map({ serializeAppleDriveExisting($0) }),
|
|
|
"networkInterfaces": config.networks.enumerated().map({ serializeAppleNetwork($1, index: $0) }),
|
|
|
"serialPorts": config.serials.enumerated().map({ serializeAppleSerial($1, index: $0) }),
|
|
|
+ "displays": config.displays.map({ serializeAppleDisplay($0)}),
|
|
|
]
|
|
|
}
|
|
|
|
|
@@ -260,6 +280,13 @@ extension UTMScriptingConfigImpl {
|
|
|
"interface": appleSerialInterface(from: config.mode).rawValue,
|
|
|
]
|
|
|
}
|
|
|
+
|
|
|
+ private func serializeAppleDisplay(_ config: UTMAppleConfigurationDisplay) -> [AnyHashable : Any] {
|
|
|
+ [
|
|
|
+ "id": config.id.uuidString,
|
|
|
+ "dynamicResolution": config.isDynamicResolution,
|
|
|
+ ]
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
@MainActor
|
|
@@ -360,6 +387,9 @@ extension UTMScriptingConfigImpl {
|
|
|
if let serialPorts = record["serialPorts"] as? [[AnyHashable : Any]] {
|
|
|
try updateQemuSerials(from: serialPorts)
|
|
|
}
|
|
|
+ if let displays = record["displays"] as? [[AnyHashable : Any]] {
|
|
|
+ try updateQemuDisplays(from: displays)
|
|
|
+ }
|
|
|
if let qemuAdditionalArguments = record["qemuAdditionalArguments"] as? [[AnyHashable: Any]] {
|
|
|
try updateQemuAdditionalArguments(from: qemuAdditionalArguments)
|
|
|
}
|
|
@@ -525,6 +555,47 @@ extension UTMScriptingConfigImpl {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private func updateQemuDisplays(from records: [[AnyHashable : Any]]) throws {
|
|
|
+ let config = config as! UTMQemuConfiguration
|
|
|
+ try updateElements(&config.displays, with: records, onExisting: updateQemuExistingDisplay, onNew: { record in
|
|
|
+ guard var newDisplay = UTMQemuConfigurationDisplay(forArchitecture: config.system.architecture, target: config.system.target) else {
|
|
|
+ throw ConfigurationError.deviceNotSupported
|
|
|
+ }
|
|
|
+ try updateQemuExistingDisplay(&newDisplay, from: record)
|
|
|
+ return newDisplay
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ private func parseQemuScaler(_ value: AEKeyword?) -> QEMUScaler? {
|
|
|
+ guard let value = value, let parsed = UTMScriptingQemuScaler(rawValue: value) else {
|
|
|
+ return Optional.none
|
|
|
+ }
|
|
|
+ switch parsed {
|
|
|
+ case .linear: return .linear
|
|
|
+ case .nearest: return .nearest
|
|
|
+ default: return Optional.none
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateQemuExistingDisplay(_ display: inout UTMQemuConfigurationDisplay, from record: [AnyHashable : Any]) throws {
|
|
|
+ let config = config as! UTMQemuConfiguration
|
|
|
+ if let hardware = record["hardware"] as? String, let hardware = config.system.architecture.displayDeviceType.init(rawValue: hardware) {
|
|
|
+ display.hardware = hardware
|
|
|
+ }
|
|
|
+ if let dynamicResolution = record["dynamicResolution"] as? Bool {
|
|
|
+ display.isDynamicResolution = dynamicResolution
|
|
|
+ }
|
|
|
+ if let nativeResolution = record["nativeResolution"] as? Bool {
|
|
|
+ display.isNativeResolution = nativeResolution
|
|
|
+ }
|
|
|
+ if let upscalingFilter = parseQemuScaler(record["upscalingFilter"] as? AEKeyword) {
|
|
|
+ display.upscalingFilter = upscalingFilter
|
|
|
+ }
|
|
|
+ if let downscalingFilter = parseQemuScaler(record["downscalingFilter"] as? AEKeyword) {
|
|
|
+ display.downscalingFilter = downscalingFilter
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private func updateQemuAdditionalArguments(from records: [[AnyHashable: Any]]) throws {
|
|
|
let config = config as! UTMQemuConfiguration
|
|
|
let additionalArguments = records.compactMap { record -> QEMUArgument? in
|
|
@@ -574,6 +645,9 @@ extension UTMScriptingConfigImpl {
|
|
|
if let serialPorts = record["serialPorts"] as? [[AnyHashable : Any]] {
|
|
|
try updateAppleSerials(from: serialPorts)
|
|
|
}
|
|
|
+ if let displays = record["displays"] as? [[AnyHashable : Any]] {
|
|
|
+ try updateAppleDisplays(from: displays)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private func updateAppleDirectoryShares(from records: [[AnyHashable : Any]]) throws {
|
|
@@ -670,6 +744,20 @@ extension UTMScriptingConfigImpl {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private func updateAppleDisplays(from records: [[AnyHashable : Any]]) throws {
|
|
|
+ let config = config as! UTMAppleConfiguration
|
|
|
+ try updateElements(&config.displays, with: records, onExisting: updateAppleExistingDisplay, onNew: { record in
|
|
|
+ var newDisplay = UTMAppleConfigurationDisplay()
|
|
|
+ try updateAppleExistingDisplay(&newDisplay, from: record)
|
|
|
+ return newDisplay
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ private func updateAppleExistingDisplay(_ display: inout UTMAppleConfigurationDisplay, from record: [AnyHashable : Any]) throws {
|
|
|
+ if let dynamicResolution = record["dynamicResolution"] as? Bool {
|
|
|
+ display.isDynamicResolution = dynamicResolution
|
|
|
+ }
|
|
|
+ }
|
|
|
enum ConfigurationError: Error, LocalizedError {
|
|
|
case identifierNotFound(id: any Hashable)
|
|
|
case invalidDriveDescription
|