Skip to content

Commit 2b86f67

Browse files
authored
Merge pull request #6978 from naveenrajm7/script-display
scripting: expose display config
2 parents 3dfcddc + 77a3617 commit 2b86f67

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

Scripting/UTM.sdef

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@
448448
<type type="qemu serial configuration" list="yes"/>
449449
</property>
450450

451+
<property name="displays" code="DiPs"
452+
description="List of display configuration.">
453+
<type type="qemu display configuration" list="yes"/>
454+
</property>
455+
451456
<property name="qemu additional arguments" code="QeAd"
452457
description="List of qemu arguments.">
453458
<type type="qemu argument" list="yes"/>
@@ -561,6 +566,31 @@
561566
description="The port number to listen on when the interface is a TCP server."/>
562567
</record-type>
563568

569+
<record-type name="qemu display configuration" code="QdYc" description="QEMU virtual display configuration.">
570+
<property name="id" code="ID " type="text" access="r"
571+
description="The unique identifier for this display (if empty, a new display will be created)."/>
572+
573+
<property name="hardware" code="HaWe" type="text"
574+
description="Name of the emulated display card (required. if given hardware not found, the default will be used)."/>
575+
576+
<property name="dynamic resolution" code="DyRe" type="boolean"
577+
description="If true, attempt to use SPICE guest agent to change the display resolution automatically."/>
578+
579+
<property name="native resolution" code="NaRe" type="boolean"
580+
description="If true, use the true (retina) resolution of the display. Otherwise, use the percieved resolution."/>
581+
582+
<property name="upscaling filter" code="UpFi" type="qemu scaler"
583+
description="Filter to use when upscaling."/>
584+
585+
<property name="downscaling filter" code="DoFi" type="qemu scaler"
586+
description="Filter to use when downscaling."/>
587+
</record-type>
588+
589+
<enumeration name="qemu scaler" code="QeSc" description="QEMU Scaler.">
590+
<enumerator name="linear" code="QsLi"/>
591+
<enumerator name="nearest" code="QsNe"/>
592+
</enumeration>
593+
564594
<record-type name="qemu argument" code="QeAr" description="QEMU argument configuration.">
565595
<property name="argument string" code="ArSt" type="text"
566596
description="The QEMU argument as a string."/>
@@ -605,6 +635,11 @@
605635
description="List of serial configuration.">
606636
<type type="apple serial configuration" list="yes"/>
607637
</property>
638+
639+
<property name="displays" code="DiPs"
640+
description="List of display configuration.">
641+
<type type="apple display configuration" list="yes"/>
642+
</property>
608643
</record-type>
609644

610645
<record-type name="apple directory share configuration" code="ApDs" description="Apple directory share configuration.">
@@ -658,6 +693,14 @@
658693
<property name="interface" code="InTf" type="serial interface"
659694
description="The type of serial interface on the host (only PTTY is supported)."/>
660695
</record-type>
696+
697+
<record-type name="apple display configuration" code="AdYc" description="Apple virtual display configuration.">
698+
<property name="id" code="ID " type="text" access="r"
699+
description="The unique identifier for this display (if empty, a new display will be created)."/>
700+
701+
<property name="dynamic resolution" code="DyRe" type="boolean"
702+
description="Dynamic Resolution."/>
703+
</record-type>
661704
</suite>
662705

663706
<suite name="UTM USB Devices Suite" code="UTMu" description="UTM virtual machine USB devices suite. Use this to connect USB devices from the host to the guest.">

Scripting/UTMScripting.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ import ScriptingBridge
132132
case udp = 0x55645070 /* 'UdPp' */
133133
}
134134

135+
// MARK: UTMScriptingQemuScaler
136+
@objc public enum UTMScriptingQemuScaler : AEKeyword {
137+
case linear = 0x51734c69 /* 'QsLi' */
138+
case nearest = 0x51734e65 /* 'QsNe' */
139+
}
140+
135141
// MARK: UTMScriptingAppleNetworkMode
136142
@objc public enum UTMScriptingAppleNetworkMode : AEKeyword {
137143
case shared = 0x53685264 /* 'ShRd' */

Scripting/UTMScriptingConfigImpl.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ extension UTMScriptingConfigImpl {
108108
"drives": config.drives.map({ serializeQemuDriveExisting($0) }),
109109
"networkInterfaces": config.networks.enumerated().map({ serializeQemuNetwork($1, index: $0) }),
110110
"serialPorts": config.serials.enumerated().map({ serializeQemuSerial($1, index: $0) }),
111+
"displays": config.displays.map({ serializeQemuDisplay($0)}),
111112
"qemuAdditionalArguments": config.qemu.additionalArguments.map({ serializeQemuAdditionalArgument($0)}),
112113
]
113114
}
@@ -190,6 +191,24 @@ extension UTMScriptingConfigImpl {
190191
]
191192
}
192193

194+
private func qemuScaler(from filter: QEMUScaler) -> UTMScriptingQemuScaler {
195+
switch filter {
196+
case .linear: return .linear
197+
case .nearest: return .nearest
198+
}
199+
}
200+
201+
private func serializeQemuDisplay(_ config: UTMQemuConfigurationDisplay) -> [AnyHashable : Any] {
202+
[
203+
"id": config.id.uuidString,
204+
"hardware": config.hardware.rawValue,
205+
"dynamicResolution": config.isDynamicResolution,
206+
"nativeResolution": config.isNativeResolution,
207+
"upscalingFilter": qemuScaler(from: config.upscalingFilter).rawValue,
208+
"downscalingFilter": qemuScaler(from: config.downscalingFilter).rawValue,
209+
]
210+
}
211+
193212
private func serializeQemuAdditionalArgument(_ argument: QEMUArgument) -> [AnyHashable: Any] {
194213
var serializedArgument: [AnyHashable: Any] = [
195214
"argumentString": argument.string
@@ -213,6 +232,7 @@ extension UTMScriptingConfigImpl {
213232
"drives": config.drives.map({ serializeAppleDriveExisting($0) }),
214233
"networkInterfaces": config.networks.enumerated().map({ serializeAppleNetwork($1, index: $0) }),
215234
"serialPorts": config.serials.enumerated().map({ serializeAppleSerial($1, index: $0) }),
235+
"displays": config.displays.map({ serializeAppleDisplay($0)}),
216236
]
217237
}
218238

@@ -260,6 +280,13 @@ extension UTMScriptingConfigImpl {
260280
"interface": appleSerialInterface(from: config.mode).rawValue,
261281
]
262282
}
283+
284+
private func serializeAppleDisplay(_ config: UTMAppleConfigurationDisplay) -> [AnyHashable : Any] {
285+
[
286+
"id": config.id.uuidString,
287+
"dynamicResolution": config.isDynamicResolution,
288+
]
289+
}
263290
}
264291

265292
@MainActor
@@ -360,6 +387,9 @@ extension UTMScriptingConfigImpl {
360387
if let serialPorts = record["serialPorts"] as? [[AnyHashable : Any]] {
361388
try updateQemuSerials(from: serialPorts)
362389
}
390+
if let displays = record["displays"] as? [[AnyHashable : Any]] {
391+
try updateQemuDisplays(from: displays)
392+
}
363393
if let qemuAdditionalArguments = record["qemuAdditionalArguments"] as? [[AnyHashable: Any]] {
364394
try updateQemuAdditionalArguments(from: qemuAdditionalArguments)
365395
}
@@ -525,6 +555,47 @@ extension UTMScriptingConfigImpl {
525555
}
526556
}
527557

558+
private func updateQemuDisplays(from records: [[AnyHashable : Any]]) throws {
559+
let config = config as! UTMQemuConfiguration
560+
try updateElements(&config.displays, with: records, onExisting: updateQemuExistingDisplay, onNew: { record in
561+
guard var newDisplay = UTMQemuConfigurationDisplay(forArchitecture: config.system.architecture, target: config.system.target) else {
562+
throw ConfigurationError.deviceNotSupported
563+
}
564+
try updateQemuExistingDisplay(&newDisplay, from: record)
565+
return newDisplay
566+
})
567+
}
568+
569+
private func parseQemuScaler(_ value: AEKeyword?) -> QEMUScaler? {
570+
guard let value = value, let parsed = UTMScriptingQemuScaler(rawValue: value) else {
571+
return Optional.none
572+
}
573+
switch parsed {
574+
case .linear: return .linear
575+
case .nearest: return .nearest
576+
default: return Optional.none
577+
}
578+
}
579+
580+
private func updateQemuExistingDisplay(_ display: inout UTMQemuConfigurationDisplay, from record: [AnyHashable : Any]) throws {
581+
let config = config as! UTMQemuConfiguration
582+
if let hardware = record["hardware"] as? String, let hardware = config.system.architecture.displayDeviceType.init(rawValue: hardware) {
583+
display.hardware = hardware
584+
}
585+
if let dynamicResolution = record["dynamicResolution"] as? Bool {
586+
display.isDynamicResolution = dynamicResolution
587+
}
588+
if let nativeResolution = record["nativeResolution"] as? Bool {
589+
display.isNativeResolution = nativeResolution
590+
}
591+
if let upscalingFilter = parseQemuScaler(record["upscalingFilter"] as? AEKeyword) {
592+
display.upscalingFilter = upscalingFilter
593+
}
594+
if let downscalingFilter = parseQemuScaler(record["downscalingFilter"] as? AEKeyword) {
595+
display.downscalingFilter = downscalingFilter
596+
}
597+
}
598+
528599
private func updateQemuAdditionalArguments(from records: [[AnyHashable: Any]]) throws {
529600
let config = config as! UTMQemuConfiguration
530601
let additionalArguments = records.compactMap { record -> QEMUArgument? in
@@ -574,6 +645,9 @@ extension UTMScriptingConfigImpl {
574645
if let serialPorts = record["serialPorts"] as? [[AnyHashable : Any]] {
575646
try updateAppleSerials(from: serialPorts)
576647
}
648+
if let displays = record["displays"] as? [[AnyHashable : Any]] {
649+
try updateAppleDisplays(from: displays)
650+
}
577651
}
578652

579653
private func updateAppleDirectoryShares(from records: [[AnyHashable : Any]]) throws {
@@ -670,6 +744,20 @@ extension UTMScriptingConfigImpl {
670744
}
671745
}
672746

747+
private func updateAppleDisplays(from records: [[AnyHashable : Any]]) throws {
748+
let config = config as! UTMAppleConfiguration
749+
try updateElements(&config.displays, with: records, onExisting: updateAppleExistingDisplay, onNew: { record in
750+
var newDisplay = UTMAppleConfigurationDisplay()
751+
try updateAppleExistingDisplay(&newDisplay, from: record)
752+
return newDisplay
753+
})
754+
}
755+
756+
private func updateAppleExistingDisplay(_ display: inout UTMAppleConfigurationDisplay, from record: [AnyHashable : Any]) throws {
757+
if let dynamicResolution = record["dynamicResolution"] as? Bool {
758+
display.isDynamicResolution = dynamicResolution
759+
}
760+
}
673761
enum ConfigurationError: Error, LocalizedError {
674762
case identifierNotFound(id: any Hashable)
675763
case invalidDriveDescription

0 commit comments

Comments
 (0)