2023-03-19 16:21:51 -07:00
//
// C o p y r i g h t © 2 0 2 3 o s y . A l l r i g h t s r e s e r v e d .
//
// L i c e n s e d u n d e r t h e A p a c h e L i c e n s e , V e r s i o n 2 . 0 ( t h e " L i c e n s e " ) ;
// y o u m a y n o t u s e t h i s f i l e e x c e p t i n c o m p l i a n c e w i t h t h e L i c e n s e .
// Y o u m a y o b t a i n a c o p y o f t h e L i c e n s e a t
//
// h t t p : / / w w w . a p a c h e . o r g / l i c e n s e s / L I C E N S E - 2 . 0
//
// U n l e s s r e q u i r e d b y a p p l i c a b l e l a w o r a g r e e d t o i n w r i t i n g , s o f t w a r e
// d i s t r i b u t e d u n d e r t h e L i c e n s e i s d i s t r i b u t e d o n a n " A S I S " B A S I S ,
// W I T H O U T W A R R A N T I E S O R C O N D I T I O N S O F A N Y K I N D , e i t h e r e x p r e s s o r i m p l i e d .
// S e e t h e L i c e n s e f o r t h e s p e c i f i c l a n g u a g e g o v e r n i n g p e r m i s s i o n s a n d
// l i m i t a t i o n s u n d e r t h e L i c e n s e .
//
import Foundation
@objc extension UTMScriptingVirtualMachineImpl {
@objc var configuration : [ AnyHashable : Any ] {
2023-06-28 19:45:05 -05:00
let wrapper = UTMScriptingConfigImpl ( vm . config , data : data )
2023-03-19 19:08:09 -07:00
return wrapper . serializeConfiguration ( )
2023-03-19 16:21:51 -07:00
}
@objc func updateConfiguration ( _ command : NSScriptCommand ) {
let newConfiguration = command . evaluatedArguments ? [ " newConfiguration " ] as ? [ AnyHashable : Any ]
withScriptCommand ( command ) { [ self ] in
guard let newConfiguration = newConfiguration else {
throw ScriptingError . invalidParameter
}
2023-06-28 19:45:05 -05:00
guard vm . state = = . stopped else {
2023-03-19 16:21:51 -07:00
throw ScriptingError . notStopped
}
2023-06-28 19:45:05 -05:00
let wrapper = UTMScriptingConfigImpl ( vm . config )
2023-03-19 19:08:09 -07:00
try wrapper . updateConfiguration ( from : newConfiguration )
2023-06-26 23:28:18 -05:00
try await data . save ( vm : box )
2023-03-19 16:21:51 -07:00
}
}
}
@ MainActor
2023-03-19 19:08:09 -07:00
class UTMScriptingConfigImpl {
2023-03-19 16:21:51 -07:00
private var bytesInMib : Int64 {
1048576
}
2023-03-19 19:08:09 -07:00
private ( set ) var config : any UTMConfiguration
private weak var data : UTMData ?
init ( _ config : any UTMConfiguration , data : UTMData ? = nil ) {
self . config = config
self . data = data
}
func serializeConfiguration ( ) -> [ AnyHashable : Any ] {
if let qemuConfig = config as ? UTMQemuConfiguration {
return serializeQemuConfiguration ( qemuConfig )
} else if let appleConfig = config as ? UTMAppleConfiguration {
return serializeAppleConfiguration ( appleConfig )
} else {
fatalError ( )
}
}
func updateConfiguration ( from record : [ AnyHashable : Any ] ) throws {
if let _ = config as ? UTMQemuConfiguration {
try updateQemuConfiguration ( from : record )
} else if let _ = config as ? UTMAppleConfiguration {
try updateAppleConfiguration ( from : record )
} else {
fatalError ( )
}
}
private func size ( of drive : any UTMConfigurationDrive ) -> Int {
guard let data = data else {
return 0
}
guard let url = drive . imageURL else {
return 0
}
return Int ( data . computeSize ( for : url ) / bytesInMib )
}
}
@ MainActor
extension UTMScriptingConfigImpl {
2023-03-19 16:21:51 -07:00
private func qemuDirectoryShareMode ( from mode : QEMUFileShareMode ) -> UTMScriptingQemuDirectoryShareMode {
switch mode {
case . none : return . none
case . webdav : return . webDAV
case . virtfs : return . virtFS
}
}
private func serializeQemuConfiguration ( _ config : UTMQemuConfiguration ) -> [ AnyHashable : Any ] {
[
" name " : config . information . name ,
2024-12-22 23:31:12 -07:00
" icon " : config . information . iconURL ? . deletingPathExtension ( ) . lastPathComponent ? ? " " ,
2023-03-19 16:21:51 -07:00
" notes " : config . information . notes ? ? " " ,
" architecture " : config . system . architecture . rawValue ,
" machine " : config . system . target . rawValue ,
" memory " : config . system . memorySize ,
" cpuCores " : config . system . cpuCount ,
" hypervisor " : config . qemu . hasHypervisor ,
" uefi " : config . qemu . hasUefiBoot ,
" directoryShareMode " : qemuDirectoryShareMode ( from : config . sharing . directoryShareMode ) . rawValue ,
" drives " : config . drives . map ( { serializeQemuDriveExisting ( $0 ) } ) ,
" networkInterfaces " : config . networks . enumerated ( ) . map ( { serializeQemuNetwork ( $1 , index : $0 ) } ) ,
" serialPorts " : config . serials . enumerated ( ) . map ( { serializeQemuSerial ( $1 , index : $0 ) } ) ,
2025-07-24 11:02:55 -07:00
" displays " : config . displays . enumerated ( ) . map ( { serializeQemuDisplay ( $1 , index : $0 ) } ) ,
2024-10-12 14:08:49 -06:00
" qemuAdditionalArguments " : config . qemu . additionalArguments . map ( { serializeQemuAdditionalArgument ( $0 ) } ) ,
2023-03-19 16:21:51 -07:00
]
}
private func qemuDriveInterface ( from interface : QEMUDriveInterface ) -> UTMScriptingQemuDriveInterface {
switch interface {
case . none : return . none
case . ide : return . ide
case . scsi : return . scsi
case . sd : return . sd
case . mtd : return . mtd
case . floppy : return . floppy
case . pflash : return . pFlash
case . virtio : return . virtIO
case . nvme : return . nvMe
case . usb : return . usb
}
}
private func serializeQemuDriveExisting ( _ config : UTMQemuConfigurationDrive ) -> [ AnyHashable : Any ] {
[
" id " : config . id ,
" removable " : config . isExternal ,
" interface " : qemuDriveInterface ( from : config . interface ) . rawValue ,
" hostSize " : size ( of : config ) ,
]
}
private func qemuNetworkMode ( from mode : QEMUNetworkMode ) -> UTMScriptingQemuNetworkMode {
switch mode {
case . emulated : return . emulated
case . shared : return . shared
case . host : return . host
case . bridged : return . bridged
}
}
private func serializeQemuNetwork ( _ config : UTMQemuConfigurationNetwork , index : Int ) -> [ AnyHashable : Any ] {
[
" index " : index ,
" hardware " : config . hardware . rawValue ,
" mode " : qemuNetworkMode ( from : config . mode ) . rawValue ,
" address " : config . macAddress ,
" hostInterface " : config . bridgeInterface ? ? " " ,
" portForwards " : config . portForward . map ( { serializeQemuPortForward ( $0 ) } ) ,
]
}
private func networkProtocol ( from protc : QEMUNetworkProtocol ) -> UTMScriptingNetworkProtocol {
switch protc {
case . tcp : return . tcp
case . udp : return . udp
}
}
private func serializeQemuPortForward ( _ config : UTMQemuConfigurationPortForward ) -> [ AnyHashable : Any ] {
[
" protocol " : networkProtocol ( from : config . protocol ) . rawValue ,
" hostAddress " : config . hostAddress ? ? " " ,
" hostPort " : config . hostPort ,
" guestAddress " : config . guestAddress ? ? " " ,
" guestPort " : config . guestPort ,
]
}
private func qemuSerialInterface ( from mode : QEMUSerialMode ) -> UTMScriptingSerialInterface {
switch mode {
case . ptty : return . ptty
case . tcpServer : return . tcp
default : return . unavailable
}
}
private func serializeQemuSerial ( _ config : UTMQemuConfigurationSerial , index : Int ) -> [ AnyHashable : Any ] {
[
" index " : index ,
" hardware " : config . hardware ? . rawValue ? ? " " ,
" interface " : qemuSerialInterface ( from : config . mode ) . rawValue ,
" port " : config . tcpPort ? ? 0 ,
]
}
2025-01-19 02:17:17 -07:00
private func qemuScaler ( from filter : QEMUScaler ) -> UTMScriptingQemuScaler {
switch filter {
case . linear : return . linear
case . nearest : return . nearest
}
}
2025-07-24 11:02:55 -07:00
private func serializeQemuDisplay ( _ config : UTMQemuConfigurationDisplay , index : Int ) -> [ AnyHashable : Any ] {
2025-01-19 02:17:17 -07:00
[
2025-07-24 11:02:55 -07:00
" index " : index ,
2025-01-19 02:17:17 -07:00
" hardware " : config . hardware . rawValue ,
" dynamicResolution " : config . isDynamicResolution ,
" nativeResolution " : config . isNativeResolution ,
" upscalingFilter " : qemuScaler ( from : config . upscalingFilter ) . rawValue ,
" downscalingFilter " : qemuScaler ( from : config . downscalingFilter ) . rawValue ,
]
}
2024-10-12 14:08:49 -06:00
private func serializeQemuAdditionalArgument ( _ argument : QEMUArgument ) -> [ AnyHashable : Any ] {
var serializedArgument : [ AnyHashable : Any ] = [
2024-10-12 22:03:57 -06:00
" argumentString " : argument . string
2024-10-12 14:08:49 -06:00
]
2025-01-17 17:30:50 -07:00
// O n l y a d d f i l e U r l s i f i t i s n o t n i l a n d c o n t a i n s U R L s
if let fileUrls = argument . fileUrls , ! fileUrls . isEmpty {
serializedArgument [ " fileUrls " ] = fileUrls . map ( { $0 as AnyHashable } )
}
2024-10-12 14:08:49 -06:00
return serializedArgument
}
2023-03-19 16:21:51 -07:00
private func serializeAppleConfiguration ( _ config : UTMAppleConfiguration ) -> [ AnyHashable : Any ] {
[
" name " : config . information . name ,
2024-12-22 23:31:12 -07:00
" icon " : config . information . iconURL ? . deletingPathExtension ( ) . lastPathComponent ? ? " " ,
2023-03-19 16:21:51 -07:00
" notes " : config . information . notes ? ? " " ,
" memory " : config . system . memorySize ,
" cpuCores " : config . system . cpuCount ,
" directoryShares " : config . sharedDirectories . enumerated ( ) . map ( { serializeAppleDirectoryShare ( $1 , index : $0 ) } ) ,
" drives " : config . drives . map ( { serializeAppleDriveExisting ( $0 ) } ) ,
" networkInterfaces " : config . networks . enumerated ( ) . map ( { serializeAppleNetwork ( $1 , index : $0 ) } ) ,
" serialPorts " : config . serials . enumerated ( ) . map ( { serializeAppleSerial ( $1 , index : $0 ) } ) ,
2025-07-24 11:02:55 -07:00
" displays " : config . displays . enumerated ( ) . map ( { serializeAppleDisplay ( $1 , index : $0 ) } ) ,
2023-03-19 16:21:51 -07:00
]
}
private func serializeAppleDirectoryShare ( _ config : UTMAppleConfigurationSharedDirectory , index : Int ) -> [ AnyHashable : Any ] {
[
" index " : index ,
" readOnly " : config . isReadOnly
]
}
private func serializeAppleDriveExisting ( _ config : UTMAppleConfigurationDrive ) -> [ AnyHashable : Any ] {
[
" id " : config . id ,
" removable " : config . isExternal ,
" hostSize " : size ( of : config ) ,
]
}
private func appleNetworkMode ( from mode : UTMAppleConfigurationNetwork . NetworkMode ) -> UTMScriptingAppleNetworkMode {
switch mode {
case . shared : return . shared
case . bridged : return . bridged
}
}
private func serializeAppleNetwork ( _ config : UTMAppleConfigurationNetwork , index : Int ) -> [ AnyHashable : Any ] {
[
" index " : index ,
" mode " : appleNetworkMode ( from : config . mode ) . rawValue ,
" address " : config . macAddress ,
" hostInterface " : config . bridgeInterface ? ? " " ,
]
}
private func appleSerialInterface ( from mode : UTMAppleConfigurationSerial . SerialMode ) -> UTMScriptingSerialInterface {
switch mode {
case . ptty : return . ptty
default : return . unavailable
}
}
private func serializeAppleSerial ( _ config : UTMAppleConfigurationSerial , index : Int ) -> [ AnyHashable : Any ] {
[
" index " : index ,
" interface " : appleSerialInterface ( from : config . mode ) . rawValue ,
]
}
2025-01-19 02:17:17 -07:00
2025-07-24 11:02:55 -07:00
private func serializeAppleDisplay ( _ config : UTMAppleConfigurationDisplay , index : Int ) -> [ AnyHashable : Any ] {
2025-01-19 02:17:17 -07:00
[
2025-07-24 11:02:55 -07:00
" index " : index ,
2025-01-19 02:17:17 -07:00
" dynamicResolution " : config . isDynamicResolution ,
]
}
2023-03-19 16:21:51 -07:00
}
@ MainActor
2023-03-19 19:08:09 -07:00
extension UTMScriptingConfigImpl {
2023-03-19 16:21:51 -07:00
private func updateElements < T > ( _ array : inout [ T ] , with records : [ [ AnyHashable : Any ] ] , onExisting : @ MainActor ( inout T , [ AnyHashable : Any ] ) throws -> Void , onNew : @ MainActor ( [ AnyHashable : Any ] ) throws -> T ) throws {
var unseenIndicies = IndexSet ( integersIn : array . indices )
for record in records {
if let index = record [ " index " ] as ? Int {
guard array . indices . contains ( index ) else {
throw ConfigurationError . indexNotFound ( index : index )
}
try onExisting ( & array [ index ] , record )
unseenIndicies . remove ( index )
} else {
array . append ( try onNew ( record ) )
}
}
array . remove ( atOffsets : unseenIndicies )
}
private func updateIdentifiedElements < T : Identifiable > ( _ array : inout [ T ] , with records : [ [ AnyHashable : Any ] ] , onExisting : @ MainActor ( inout T , [ AnyHashable : Any ] ) throws -> Void , onNew : @ MainActor ( [ AnyHashable : Any ] ) throws -> T ) throws {
var unseenIndicies = IndexSet ( integersIn : array . indices )
for record in records {
if let id = record [ " id " ] as ? T . ID {
guard let index = array . enumerated ( ) . first ( where : { $1 . id = = id } ) ? . offset else {
throw ConfigurationError . identifierNotFound ( id : id )
}
try onExisting ( & array [ index ] , record )
unseenIndicies . remove ( index )
} else {
array . append ( try onNew ( record ) )
}
}
array . remove ( atOffsets : unseenIndicies )
}
private func parseQemuDirectoryShareMode ( _ value : AEKeyword ? ) -> QEMUFileShareMode ? {
guard let value = value , let parsed = UTMScriptingQemuDirectoryShareMode ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . none : return QEMUFileShareMode . none
case . webDAV : return . webdav
case . virtFS : return . virtfs
default : return Optional . none
}
}
private func updateQemuConfiguration ( from record : [ AnyHashable : Any ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
if let name = record [ " name " ] as ? String , ! name . isEmpty {
config . information . name = name
}
2024-12-22 23:31:12 -07:00
if let icon = record [ " icon " ] as ? String , ! icon . isEmpty {
if let url = UTMConfigurationInfo . builtinIcon ( named : icon ) {
config . information . iconURL = url
} else {
throw ConfigurationError . iconNotFound ( icon : icon )
}
}
2023-03-19 16:21:51 -07:00
if let notes = record [ " notes " ] as ? String , ! notes . isEmpty {
config . information . notes = notes
}
let architecture = record [ " architecture " ] as ? String
let arch = QEMUArchitecture ( rawValue : architecture ? ? " " )
let machine = record [ " machine " ] as ? String
let target = arch ? . targetType . init ( rawValue : machine ? ? " " )
if let arch = arch , arch != config . system . architecture {
let target = target ? ? arch . targetType . default
config . system . architecture = arch
config . system . target = target
config . reset ( forArchitecture : arch , target : target )
2023-03-20 12:00:54 -07:00
} else if let target = target , target . rawValue != config . system . target . rawValue {
2023-03-19 16:21:51 -07:00
config . system . target = target
config . reset ( forArchitecture : config . system . architecture , target : target )
}
if let memory = record [ " memory " ] as ? Int , memory != 0 {
config . system . memorySize = memory
}
if let cpuCores = record [ " cpuCores " ] as ? Int {
config . system . cpuCount = cpuCores
}
if let hypervisor = record [ " hypervisor " ] as ? Bool {
config . qemu . hasHypervisor = hypervisor
}
if let uefi = record [ " uefi " ] as ? Bool {
config . qemu . hasUefiBoot = uefi
}
if let directoryShareMode = parseQemuDirectoryShareMode ( record [ " directoryShareMode " ] as ? AEKeyword ) {
config . sharing . directoryShareMode = directoryShareMode
}
if let drives = record [ " drives " ] as ? [ [ AnyHashable : Any ] ] {
try updateQemuDrives ( from : drives )
}
if let networkInterfaces = record [ " networkInterfaces " ] as ? [ [ AnyHashable : Any ] ] {
try updateQemuNetworks ( from : networkInterfaces )
}
if let serialPorts = record [ " serialPorts " ] as ? [ [ AnyHashable : Any ] ] {
try updateQemuSerials ( from : serialPorts )
}
2025-01-19 02:17:17 -07:00
if let displays = record [ " displays " ] as ? [ [ AnyHashable : Any ] ] {
try updateQemuDisplays ( from : displays )
}
2024-10-12 14:08:49 -06:00
if let qemuAdditionalArguments = record [ " qemuAdditionalArguments " ] as ? [ [ AnyHashable : Any ] ] {
try updateQemuAdditionalArguments ( from : qemuAdditionalArguments )
}
2023-03-19 16:21:51 -07:00
}
private func parseQemuDriveInterface ( _ value : AEKeyword ? ) -> QEMUDriveInterface ? {
guard let value = value , let parsed = UTMScriptingQemuDriveInterface ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . none : return QEMUDriveInterface . none
case . ide : return . ide
case . scsi : return . scsi
case . sd : return . sd
case . mtd : return . mtd
case . floppy : return . floppy
case . pFlash : return . pflash
case . virtIO : return . virtio
case . nvMe : return . nvme
case . usb : return . usb
default : return Optional . none
}
}
private func updateQemuDrives ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
try updateIdentifiedElements ( & config . drives , with : records , onExisting : updateQemuExistingDrive , onNew : unserializeQemuDriveNew )
}
private func updateQemuExistingDrive ( _ drive : inout UTMQemuConfigurationDrive , from record : [ AnyHashable : Any ] ) throws {
if let interface = parseQemuDriveInterface ( record [ " interface " ] as ? AEKeyword ) {
drive . interface = interface
}
2023-03-20 12:00:11 -07:00
if let source = record [ " source " ] as ? URL {
drive . imageURL = source
}
2023-03-19 16:21:51 -07:00
}
private func unserializeQemuDriveNew ( from record : [ AnyHashable : Any ] ) throws -> UTMQemuConfigurationDrive {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
let removable = record [ " removable " ] as ? Bool ? ? false
var newDrive = UTMQemuConfigurationDrive ( forArchitecture : config . system . architecture , target : config . system . target , isExternal : removable )
if let importUrl = record [ " source " ] as ? URL {
newDrive . imageURL = importUrl
} else if let size = record [ " guestSize " ] as ? Int {
newDrive . sizeMib = size
}
if let interface = parseQemuDriveInterface ( record [ " interface " ] as ? AEKeyword ) {
newDrive . interface = interface
}
if let raw = record [ " raw " ] as ? Bool {
newDrive . isRawImage = raw
}
return newDrive
}
private func updateQemuNetworks ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
try updateElements ( & config . networks , with : records , onExisting : updateQemuExistingNetwork , onNew : { record in
guard var newNetwork = UTMQemuConfigurationNetwork ( forArchitecture : config . system . architecture , target : config . system . target ) else {
throw ConfigurationError . deviceNotSupported
}
try updateQemuExistingNetwork ( & newNetwork , from : record )
return newNetwork
} )
}
private func parseQemuNetworkMode ( _ value : AEKeyword ? ) -> QEMUNetworkMode ? {
guard let value = value , let parsed = UTMScriptingQemuNetworkMode ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . emulated : return . emulated
case . shared : return . shared
case . host : return . host
case . bridged : return . bridged
default : return . none
}
}
private func updateQemuExistingNetwork ( _ network : inout UTMQemuConfigurationNetwork , from record : [ AnyHashable : Any ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
if let hardware = record [ " hardware " ] as ? String , let hardware = config . system . architecture . networkDeviceType . init ( rawValue : hardware ) {
network . hardware = hardware
}
if let mode = parseQemuNetworkMode ( record [ " mode " ] as ? AEKeyword ) {
network . mode = mode
}
if let address = record [ " address " ] as ? String , ! address . isEmpty {
network . macAddress = address
}
if let interface = record [ " hostInterface " ] as ? String , ! interface . isEmpty {
network . bridgeInterface = interface
}
if let portForwards = record [ " portForwards " ] as ? [ [ AnyHashable : Any ] ] {
network . portForward = portForwards . map ( { unserializeQemuPortForward ( from : $0 ) } )
}
}
private func parseNetworkProtocol ( _ value : AEKeyword ? ) -> QEMUNetworkProtocol ? {
guard let value = value , let parsed = UTMScriptingNetworkProtocol ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . tcp : return . tcp
case . udp : return . udp
default : return Optional . none
}
}
private func unserializeQemuPortForward ( from record : [ AnyHashable : Any ] ) -> UTMQemuConfigurationPortForward {
var forward = UTMQemuConfigurationPortForward ( )
if let protoc = parseNetworkProtocol ( record [ " protocol " ] as ? AEKeyword ) {
forward . protocol = protoc
}
if let hostAddress = record [ " hostAddress " ] as ? String , ! hostAddress . isEmpty {
forward . hostAddress = hostAddress
}
if let hostPort = record [ " hostPort " ] as ? Int {
forward . hostPort = hostPort
}
if let guestAddress = record [ " guestAddress " ] as ? String , ! guestAddress . isEmpty {
forward . guestAddress = guestAddress
}
if let guestPort = record [ " guestPort " ] as ? Int {
forward . guestPort = guestPort
}
return forward
}
private func updateQemuSerials ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
try updateElements ( & config . serials , with : records , onExisting : updateQemuExistingSerial , onNew : { record in
guard var newSerial = UTMQemuConfigurationSerial ( forArchitecture : config . system . architecture , target : config . system . target ) else {
throw ConfigurationError . deviceNotSupported
}
try updateQemuExistingSerial ( & newSerial , from : record )
return newSerial
} )
}
private func parseQemuSerialInterface ( _ value : AEKeyword ? ) -> QEMUSerialMode ? {
guard let value = value , let parsed = UTMScriptingSerialInterface ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . ptty : return . ptty
case . tcp : return . tcpServer
default : return Optional . none
}
}
private func updateQemuExistingSerial ( _ serial : inout UTMQemuConfigurationSerial , from record : [ AnyHashable : Any ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMQemuConfiguration
2023-03-19 16:21:51 -07:00
if let hardware = record [ " hardware " ] as ? String , let hardware = config . system . architecture . serialDeviceType . init ( rawValue : hardware ) {
serial . hardware = hardware
}
if let interface = parseQemuSerialInterface ( record [ " interface " ] as ? AEKeyword ) {
serial . mode = interface
}
if let port = record [ " port " ] as ? Int {
serial . tcpPort = port
}
}
2025-01-19 02:17:17 -07:00
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
}
}
2024-10-12 14:08:49 -06:00
private func updateQemuAdditionalArguments ( from records : [ [ AnyHashable : Any ] ] ) throws {
let config = config as ! UTMQemuConfiguration
let additionalArguments = records . compactMap { record -> QEMUArgument ? in
2024-10-12 22:03:57 -06:00
guard let argumentString = record [ " argumentString " ] as ? String else { return nil }
2024-10-12 14:08:49 -06:00
var argument = QEMUArgument ( argumentString )
2025-01-17 17:30:50 -07:00
// f i l e U r l s a r e u s e d a s r e q u i r e d r e s o u r c e s b y Q E M U .
if let fileUrls = record [ " fileUrls " ] as ? [ URL ] {
argument . fileUrls = fileUrls
}
2024-10-12 14:08:49 -06:00
return argument
}
// U p d a t e e n t i r e a d d i t i o n a l a r g u m e n t s w i t h n e w o n e .
config . qemu . additionalArguments = additionalArguments
}
2023-03-19 16:21:51 -07:00
private func updateAppleConfiguration ( from record : [ AnyHashable : Any ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMAppleConfiguration
2023-03-19 16:21:51 -07:00
if let name = record [ " name " ] as ? String , ! name . isEmpty {
config . information . name = name
}
2024-12-22 23:31:12 -07:00
if let icon = record [ " icon " ] as ? String , ! icon . isEmpty {
if let url = UTMConfigurationInfo . builtinIcon ( named : icon ) {
config . information . iconURL = url
} else {
throw ConfigurationError . iconNotFound ( icon : icon )
}
}
2023-03-19 16:21:51 -07:00
if let notes = record [ " notes " ] as ? String , ! notes . isEmpty {
config . information . notes = notes
}
if let memory = record [ " memory " ] as ? Int , memory != 0 {
config . system . memorySize = memory
}
if let cpuCores = record [ " cpuCores " ] as ? Int {
config . system . cpuCount = cpuCores
}
if let directoryShares = record [ " directoryShares " ] as ? [ [ AnyHashable : Any ] ] {
try updateAppleDirectoryShares ( from : directoryShares )
}
if let drives = record [ " drives " ] as ? [ [ AnyHashable : Any ] ] {
try updateAppleDrives ( from : drives )
}
if let networkInterfaces = record [ " networkInterfaces " ] as ? [ [ AnyHashable : Any ] ] {
try updateAppleNetworks ( from : networkInterfaces )
}
if let serialPorts = record [ " serialPorts " ] as ? [ [ AnyHashable : Any ] ] {
try updateAppleSerials ( from : serialPorts )
}
2025-01-19 02:17:17 -07:00
if let displays = record [ " displays " ] as ? [ [ AnyHashable : Any ] ] {
try updateAppleDisplays ( from : displays )
}
2023-03-19 16:21:51 -07:00
}
private func updateAppleDirectoryShares ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMAppleConfiguration
2023-03-19 16:21:51 -07:00
try updateElements ( & config . sharedDirectories , with : records , onExisting : updateAppleExistingDirectoryShare , onNew : { record in
var newShare = UTMAppleConfigurationSharedDirectory ( directoryURL : nil , isReadOnly : false )
try updateAppleExistingDirectoryShare ( & newShare , from : record )
return newShare
} )
}
private func updateAppleExistingDirectoryShare ( _ share : inout UTMAppleConfigurationSharedDirectory , from record : [ AnyHashable : Any ] ) throws {
if let readOnly = record [ " readOnly " ] as ? Bool {
share . isReadOnly = readOnly
}
}
private func updateAppleDrives ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMAppleConfiguration
2023-03-20 12:00:11 -07:00
try updateIdentifiedElements ( & config . drives , with : records , onExisting : updateAppleExistingDrive , onNew : unserializeAppleNewDrive )
}
private func updateAppleExistingDrive ( _ drive : inout UTMAppleConfigurationDrive , from record : [ AnyHashable : Any ] ) throws {
if let source = record [ " source " ] as ? URL {
drive . imageURL = source
}
2023-03-19 16:21:51 -07:00
}
private func unserializeAppleNewDrive ( from record : [ AnyHashable : Any ] ) throws -> UTMAppleConfigurationDrive {
let removable = record [ " removable " ] as ? Bool ? ? false
var newDrive : UTMAppleConfigurationDrive
if let size = record [ " guestSize " ] as ? Int {
newDrive = UTMAppleConfigurationDrive ( newSize : size )
} else {
newDrive = UTMAppleConfigurationDrive ( existingURL : record [ " source " ] as ? URL , isExternal : removable )
}
return newDrive
}
private func updateAppleNetworks ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMAppleConfiguration
2023-03-19 16:21:51 -07:00
try updateElements ( & config . networks , with : records , onExisting : updateAppleExistingNetwork , onNew : { record in
var newNetwork = UTMAppleConfigurationNetwork ( )
try updateAppleExistingNetwork ( & newNetwork , from : record )
return newNetwork
} )
}
private func parseAppleNetworkMode ( _ value : AEKeyword ? ) -> UTMAppleConfigurationNetwork . NetworkMode ? {
guard let value = value , let parsed = UTMScriptingQemuNetworkMode ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . shared : return . shared
case . bridged : return . bridged
default : return Optional . none
}
}
private func updateAppleExistingNetwork ( _ network : inout UTMAppleConfigurationNetwork , from record : [ AnyHashable : Any ] ) throws {
if let mode = parseAppleNetworkMode ( record [ " mode " ] as ? AEKeyword ) {
network . mode = mode
}
if let address = record [ " address " ] as ? String , ! address . isEmpty {
network . macAddress = address
}
if let interface = record [ " hostInterface " ] as ? String , ! interface . isEmpty {
network . bridgeInterface = interface
}
}
private func updateAppleSerials ( from records : [ [ AnyHashable : Any ] ] ) throws {
2023-03-19 19:08:09 -07:00
let config = config as ! UTMAppleConfiguration
2023-03-19 16:21:51 -07:00
try updateElements ( & config . serials , with : records , onExisting : updateAppleExistingSerial , onNew : { record in
var newSerial = UTMAppleConfigurationSerial ( )
try updateAppleExistingSerial ( & newSerial , from : record )
return newSerial
} )
}
private func parseAppleSerialInterface ( _ value : AEKeyword ? ) -> UTMAppleConfigurationSerial . SerialMode ? {
guard let value = value , let parsed = UTMScriptingSerialInterface ( rawValue : value ) else {
return Optional . none
}
switch parsed {
case . ptty : return . ptty
default : return Optional . none
}
}
private func updateAppleExistingSerial ( _ serial : inout UTMAppleConfigurationSerial , from record : [ AnyHashable : Any ] ) throws {
if let interface = parseAppleSerialInterface ( record [ " interface " ] as ? AEKeyword ) {
serial . mode = interface
}
}
2025-01-19 02:17:17 -07:00
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
}
}
2023-03-19 16:21:51 -07:00
enum ConfigurationError : Error , LocalizedError {
case identifierNotFound ( id : any Hashable )
case invalidDriveDescription
case indexNotFound ( index : Int )
case deviceNotSupported
2024-12-22 23:31:12 -07:00
case iconNotFound ( icon : String )
2023-03-19 16:21:51 -07:00
var errorDescription : String ? {
switch self {
2023-06-02 20:54:53 +09:00
case . identifierNotFound ( let id ) : return String . localizedStringWithFormat ( NSLocalizedString ( " Identifier '%@' cannot be found. " , comment : " UTMScriptingConfigImpl " ) , String ( describing : id ) )
2023-03-19 16:21:51 -07:00
case . invalidDriveDescription : return NSLocalizedString ( " Drive description is invalid. " , comment : " UTMScriptingConfigImpl " )
2023-06-02 20:54:53 +09:00
case . indexNotFound ( let index ) : return String . localizedStringWithFormat ( NSLocalizedString ( " Index %lld cannot be found. " , comment : " UTMScriptingConfigImpl " ) , index )
2023-03-19 16:21:51 -07:00
case . deviceNotSupported : return NSLocalizedString ( " This device is not supported by the target. " , comment : " UTMScriptingConfigImpl " )
2024-12-22 23:31:12 -07:00
case . iconNotFound ( let icon ) : return String . localizedStringWithFormat ( NSLocalizedString ( " The icon named '%@' cannot be found in the built-in icons. " , comment : " UTMScriptingConfigImpl " ) , icon )
2023-03-19 16:21:51 -07:00
}
}
}
}