Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripting: expose display config #6978

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Scripting/UTM.sdef
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,11 @@
<type type="qemu serial configuration" list="yes"/>
</property>

<property name="displays" code="DiPs"
description="List of display configuration.">
<type type="qemu display configuration" list="yes"/>
</property>

<property name="qemu additional arguments" code="QeAd"
description="List of qemu arguments.">
<type type="qemu argument" list="yes"/>
Expand Down Expand Up @@ -558,6 +563,31 @@
description="The port number to listen on when the interface is a TCP server."/>
</record-type>

<record-type name="qemu display configuration" code="QdYc" description="QEMU virtual display configuration.">
<property name="id" code="ID " type="text" access="r"
description="The unique identifier for this display (if empty, a new display will be created)."/>

<property name="hardware" code="HaWe" type="text"
description="Name of the emulated display card (required. if given hardware not found, the default will be used)."/>

<property name="dynamic resolution" code="DyRe" type="boolean"
description="If true, attempt to use SPICE guest agent to change the display resolution automatically."/>

<property name="native resolution" code="NaRe" type="boolean"
description="If true, use the true (retina) resolution of the display. Otherwise, use the percieved resolution."/>

<property name="upscaling filter" code="UpFi" type="qemu scaler"
description="Filter to use when upscaling."/>

<property name="downscaling filter" code="DoFi" type="qemu scaler"
description="Filter to use when downscaling."/>
</record-type>

<enumeration name="qemu scaler" code="QeSc" description="QEMU Scaler.">
<enumerator name="linear" code="QsLi"/>
<enumerator name="nearest" code="QsNe"/>
</enumeration>

<record-type name="qemu argument" code="QeAr" description="QEMU argument configuration.">
<property name="argument string" code="ArSt" type="text"
description="The QEMU argument as a string."/>
Expand Down Expand Up @@ -595,6 +625,11 @@
description="List of serial configuration.">
<type type="apple serial configuration" list="yes"/>
</property>

<property name="displays" code="DiPs"
description="List of display configuration.">
<type type="apple display configuration" list="yes"/>
</property>
</record-type>

<record-type name="apple directory share configuration" code="ApDs" description="Apple directory share configuration.">
Expand Down Expand Up @@ -648,6 +683,14 @@
<property name="interface" code="InTf" type="serial interface"
description="The type of serial interface on the host (only PTTY is supported)."/>
</record-type>

<record-type name="apple display configuration" code="AdYc" description="Apple virtual display configuration.">
<property name="id" code="ID " type="text" access="r"
description="The unique identifier for this display (if empty, a new display will be created)."/>

<property name="dynamic resolution" code="DyRe" type="boolean"
description="Dynamic Resolution."/>
</record-type>
</suite>

<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.">
Expand Down
6 changes: 6 additions & 0 deletions Scripting/UTMScripting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,12 @@ import ScriptingBridge
case udp = 0x55645070 /* 'UdPp' */
}

// MARK: UTMScriptingQemuScaler
@objc public enum UTMScriptingQemuScaler : AEKeyword {
case linear = 0x51734c69 /* 'QsLi' */
case nearest = 0x51734e65 /* 'QsNe' */
}

// MARK: UTMScriptingAppleNetworkMode
@objc public enum UTMScriptingAppleNetworkMode : AEKeyword {
case shared = 0x53685264 /* 'ShRd' */
Expand Down
88 changes: 88 additions & 0 deletions Scripting/UTMScriptingConfigImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,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)}),
]
}
Expand Down Expand Up @@ -189,6 +190,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
Expand All @@ -207,6 +226,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)}),
]
}

Expand Down Expand Up @@ -254,6 +274,13 @@ extension UTMScriptingConfigImpl {
"interface": appleSerialInterface(from: config.mode).rawValue,
]
}

private func serializeAppleDisplay(_ config: UTMAppleConfigurationDisplay) -> [AnyHashable : Any] {
[
"id": config.id.uuidString,
"dynamicResolution": config.isDynamicResolution,
]
}
}

@MainActor
Expand Down Expand Up @@ -347,6 +374,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)
}
Expand Down Expand Up @@ -512,6 +542,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
Expand Down Expand Up @@ -551,6 +622,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 {
Expand Down Expand Up @@ -647,6 +721,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
Expand Down
Loading