瀏覽代碼

Merge pull request #6978 from naveenrajm7/script-display

scripting: expose display config
osy 4 月之前
父節點
當前提交
2b86f67d99
共有 3 個文件被更改,包括 137 次插入0 次删除
  1. 43 0
      Scripting/UTM.sdef
  2. 6 0
      Scripting/UTMScripting.swift
  3. 88 0
      Scripting/UTMScriptingConfigImpl.swift

+ 43 - 0
Scripting/UTM.sdef

@@ -448,6 +448,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"/>
@@ -561,6 +566,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."/>
@@ -605,6 +635,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.">
@@ -658,6 +693,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.">

+ 6 - 0
Scripting/UTMScripting.swift

@@ -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' */

+ 88 - 0
Scripting/UTMScriptingConfigImpl.swift

@@ -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