diff --git a/Configuration/UTMAppleConfiguration.swift b/Configuration/UTMAppleConfiguration.swift index 5cfe8239d..240dfc189 100644 --- a/Configuration/UTMAppleConfiguration.swift +++ b/Configuration/UTMAppleConfiguration.swift @@ -21,21 +21,21 @@ import Virtualization @available(macOS 11, *) final class UTMAppleConfiguration: UTMConfiguration { /// Basic information and icon - @Published var information: UTMConfigurationInfo = .init() + @Published var _information: UTMConfigurationInfo = .init() - @Published var system: UTMAppleConfigurationSystem = .init() + @Published private var _system: UTMAppleConfigurationSystem = .init() - @Published var virtualization: UTMAppleConfigurationVirtualization = .init() + @Published private var _virtualization: UTMAppleConfigurationVirtualization = .init() - @Published var sharedDirectories: [UTMAppleConfigurationSharedDirectory] = [] + @Published private var _sharedDirectories: [UTMAppleConfigurationSharedDirectory] = [] - @Published var displays: [UTMAppleConfigurationDisplay] = [.init()] + @Published private var _displays: [UTMAppleConfigurationDisplay] = [.init()] - @Published var drives: [UTMAppleConfigurationDrive] = [] + @Published private var _drives: [UTMAppleConfigurationDrive] = [] - @Published var networks: [UTMAppleConfigurationNetwork] = [.init()] + @Published private var _networks: [UTMAppleConfigurationNetwork] = [.init()] - @Published var serials: [UTMAppleConfigurationSerial] = [] + @Published private var _serials: [UTMAppleConfigurationSerial] = [] var backend: UTMBackend { .apple @@ -70,32 +70,32 @@ final class UTMAppleConfiguration: UTMConfiguration { guard version <= Self.currentVersion else { throw UTMConfigurationError.versionTooHigh } - information = try values.decode(UTMConfigurationInfo.self, forKey: .information) - system = try values.decode(UTMAppleConfigurationSystem.self, forKey: .system) - virtualization = try values.decode(UTMAppleConfigurationVirtualization.self, forKey: .virtualization) - sharedDirectories = try values.decode([UTMAppleConfigurationSharedDirectory].self, forKey: .sharedDirectories) - displays = try values.decode([UTMAppleConfigurationDisplay].self, forKey: .displays) - drives = try values.decode([UTMAppleConfigurationDrive].self, forKey: .drives) - networks = try values.decode([UTMAppleConfigurationNetwork].self, forKey: .networks) - serials = try values.decode([UTMAppleConfigurationSerial].self, forKey: .serials) + _information = try values.decode(UTMConfigurationInfo.self, forKey: .information) + _system = try values.decode(UTMAppleConfigurationSystem.self, forKey: .system) + _virtualization = try values.decode(UTMAppleConfigurationVirtualization.self, forKey: .virtualization) + _sharedDirectories = try values.decode([UTMAppleConfigurationSharedDirectory].self, forKey: .sharedDirectories) + _displays = try values.decode([UTMAppleConfigurationDisplay].self, forKey: .displays) + _drives = try values.decode([UTMAppleConfigurationDrive].self, forKey: .drives) + _networks = try values.decode([UTMAppleConfigurationNetwork].self, forKey: .networks) + _serials = try values.decode([UTMAppleConfigurationSerial].self, forKey: .serials) // remove incompatible configurations - if #unavailable(macOS 13), system.boot.operatingSystem != .macOS { - displays = [] + if #unavailable(macOS 13), _system.boot.operatingSystem != .macOS { + _displays = [] } else if #unavailable(macOS 12) { - displays = [] + _displays = [] } } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(information, forKey: .information) - try container.encode(system, forKey: .system) - try container.encode(virtualization, forKey: .virtualization) - try container.encode(sharedDirectories, forKey: .sharedDirectories) - try container.encode(displays, forKey: .displays) - try container.encode(drives, forKey: .drives) - try container.encode(networks, forKey: .networks) - try container.encode(serials, forKey: .serials) + try container.encode(_information, forKey: .information) + try container.encode(_system, forKey: .system) + try container.encode(_virtualization, forKey: .virtualization) + try container.encode(_sharedDirectories, forKey: .sharedDirectories) + try container.encode(_displays, forKey: .displays) + try container.encode(_drives, forKey: .drives) + try container.encode(_networks, forKey: .networks) + try container.encode(_serials, forKey: .serials) try container.encode(UTMBackend.apple, forKey: .backend) try container.encode(Self.currentVersion, forKey: .configurationVersion) } @@ -126,30 +126,120 @@ extension UTMAppleConfigurationError: LocalizedError { } } +// MARK: - Public accessors + +@MainActor extension UTMAppleConfiguration { + var information: UTMConfigurationInfo { + get { + _information + } + + set { + _information = newValue + } + } + + var system: UTMAppleConfigurationSystem { + get { + _system + } + + set { + _system = newValue + } + } + + var virtualization: UTMAppleConfigurationVirtualization { + get { + _virtualization + } + + set { + _virtualization = newValue + } + } + + var sharedDirectories: [UTMAppleConfigurationSharedDirectory] { + get { + _sharedDirectories + } + + set { + _sharedDirectories = newValue + } + } + + var sharedDirectoriesPublisher: Published<[UTMAppleConfigurationSharedDirectory]>.Publisher { + get { + $_sharedDirectories + } + } + + var displays: [UTMAppleConfigurationDisplay] { + get { + _displays + } + + set { + _displays = newValue + } + } + + var drives: [UTMAppleConfigurationDrive] { + get { + _drives + } + + set { + _drives = newValue + } + } + + var networks: [UTMAppleConfigurationNetwork] { + get { + _networks + } + + set { + _networks = newValue + } + } + + var serials: [UTMAppleConfigurationSerial] { + get { + _serials + } + + set { + _serials = newValue + } + } +} + // MARK: - Conversion of old config format extension UTMAppleConfiguration { convenience init(migrating oldConfig: UTMLegacyAppleConfiguration, dataURL: URL) { self.init() - information = .init(migrating: oldConfig, dataURL: dataURL) - system = .init(migrating: oldConfig) - virtualization = .init(migrating: oldConfig) - sharedDirectories = oldConfig.sharedDirectories.map { .init(migrating: $0) } + _information = .init(migrating: oldConfig, dataURL: dataURL) + _system = .init(migrating: oldConfig) + _virtualization = .init(migrating: oldConfig) + _sharedDirectories = oldConfig.sharedDirectories.map { .init(migrating: $0) } #if arch(arm64) if #available(macOS 12, *) { - displays = oldConfig.displays.map { .init(migrating: $0) } + _displays = oldConfig.displays.map { .init(migrating: $0) } } #endif - drives = oldConfig.diskImages.map { .init(migrating: $0) } - networks = oldConfig.networkDevices.map { .init(migrating: $0) } + _drives = oldConfig.diskImages.map { .init(migrating: $0) } + _networks = oldConfig.networkDevices.map { .init(migrating: $0) } if oldConfig.isConsoleDisplay { var serial = UTMAppleConfigurationSerial() serial.terminal = .init(migrating: oldConfig) - serials = [serial] + _serials = [serial] } else if oldConfig.isSerialEnabled { var serial = UTMAppleConfigurationSerial() serial.mode = .ptty - serials = [serial] + _serials = [serial] } } } @@ -158,7 +248,7 @@ extension UTMAppleConfiguration { @available(iOS, unavailable, message: "Apple Virtualization not available on iOS") @available(macOS 11, *) -extension UTMAppleConfiguration { +@MainActor extension UTMAppleConfiguration { var appleVZConfiguration: VZVirtualMachineConfiguration { let vzconfig = VZVirtualMachineConfiguration() system.fillVZConfiguration(vzconfig) @@ -213,19 +303,19 @@ extension UTMAppleConfiguration { @available(iOS, unavailable, message: "Apple Virtualization not available on iOS") @available(macOS 11, *) -extension UTMAppleConfiguration { +@MainActor extension UTMAppleConfiguration { func prepareSave(for packageURL: URL) async throws { try await virtualization.prepareSave(for: packageURL) } func saveData(to dataURL: URL) async throws -> [URL] { var existingDataURLs = [URL]() - existingDataURLs += try await information.saveData(to: dataURL) - existingDataURLs += try await system.boot.saveData(to: dataURL) + existingDataURLs += try await _information.saveData(to: dataURL) + existingDataURLs += try await _system.boot.saveData(to: dataURL) #if arch(arm64) if #available(macOS 12, *), system.macPlatform != nil { - existingDataURLs += try await system.macPlatform!.saveData(to: dataURL) + existingDataURLs += try await _system.macPlatform!.saveData(to: dataURL) } #endif @@ -233,9 +323,23 @@ extension UTMAppleConfiguration { try appleVZConfiguration.validate() for i in 0.. Void) { - guard let qemuConfig = qemuConfig, let efiVarsURL = qemuConfig.qemu.efiVarsURL else { + guard let qemuConfig = qemuConfig, let efiVarsURL = qemuConfig._qemu.efiVarsURL else { completion(nil) return } @@ -344,7 +348,7 @@ import Foundation } Task { do { - _ = try await qemuConfig.qemu.saveData(to: efiVarsURL.deletingLastPathComponent(), for: qemuConfig.system) + _ = try await qemuConfig._qemu.saveData(to: efiVarsURL.deletingLastPathComponent(), for: qemuConfig._system) completion(nil) } catch { completion(error) diff --git a/Configuration/UTMQemuConfiguration+Arguments.swift b/Configuration/UTMQemuConfiguration+Arguments.swift index 8d2486a03..d6773cdf4 100644 --- a/Configuration/UTMQemuConfiguration+Arguments.swift +++ b/Configuration/UTMQemuConfiguration+Arguments.swift @@ -17,7 +17,7 @@ import Foundation /// Build QEMU arguments from config -extension UTMQemuConfiguration { +@MainActor extension UTMQemuConfiguration { /// Helper function to generate a final argument /// - Parameter string: Argument fragment /// - Returns: Final argument fragment diff --git a/Configuration/UTMQemuConfiguration.swift b/Configuration/UTMQemuConfiguration.swift index b561ca838..a3f799f11 100644 --- a/Configuration/UTMQemuConfiguration.swift +++ b/Configuration/UTMQemuConfiguration.swift @@ -19,34 +19,34 @@ import Foundation /// Settings for a QEMU configuration final class UTMQemuConfiguration: UTMConfiguration { /// Basic information and icon - @Published var information: UTMConfigurationInfo = .init() + @Published var _information: UTMConfigurationInfo = .init() /// System settings - @Published var system: UTMQemuConfigurationSystem = .init() + @Published var _system: UTMQemuConfigurationSystem = .init() /// Additional QEMU tweaks - @Published var qemu: UTMQemuConfigurationQEMU = .init() + @Published var _qemu: UTMQemuConfigurationQEMU = .init() /// Input settings - @Published var input: UTMQemuConfigurationInput = .init() + @Published var _input: UTMQemuConfigurationInput = .init() /// Sharing settings - @Published var sharing: UTMQemuConfigurationSharing = .init() + @Published var _sharing: UTMQemuConfigurationSharing = .init() /// All displays - @Published var displays: [UTMQemuConfigurationDisplay] = [] + @Published var _displays: [UTMQemuConfigurationDisplay] = [] /// All drives - @Published var drives: [UTMQemuConfigurationDrive] = [] + @Published var _drives: [UTMQemuConfigurationDrive] = [] /// All network adapters - @Published var networks: [UTMQemuConfigurationNetwork] = [] + @Published var _networks: [UTMQemuConfigurationNetwork] = [] /// All serial ouputs - @Published var serials: [UTMQemuConfigurationSerial] = [] + @Published var _serials: [UTMQemuConfigurationSerial] = [] /// All audio devices - @Published var sound: [UTMQemuConfigurationSound] = [] + @Published var _sound: [UTMQemuConfigurationSound] = [] /// True if configuration is migrated from a legacy config. Not saved. private(set) var isLegacy: Bool = false @@ -87,30 +87,30 @@ final class UTMQemuConfiguration: UTMConfiguration { guard version <= Self.currentVersion else { throw UTMConfigurationError.versionTooHigh } - information = try values.decode(UTMConfigurationInfo.self, forKey: .information) - system = try values.decode(UTMQemuConfigurationSystem.self, forKey: .system) - qemu = try values.decode(UTMQemuConfigurationQEMU.self, forKey: .qemu) - input = try values.decode(UTMQemuConfigurationInput.self, forKey: .input) - sharing = try values.decode(UTMQemuConfigurationSharing.self, forKey: .sharing) - displays = try values.decode([UTMQemuConfigurationDisplay].self, forKey: .displays) - drives = try values.decode([UTMQemuConfigurationDrive].self, forKey: .drives) - networks = try values.decode([UTMQemuConfigurationNetwork].self, forKey: .networks) - serials = try values.decode([UTMQemuConfigurationSerial].self, forKey: .serials) - sound = try values.decode([UTMQemuConfigurationSound].self, forKey: .sound) + _information = try values.decode(UTMConfigurationInfo.self, forKey: .information) + _system = try values.decode(UTMQemuConfigurationSystem.self, forKey: .system) + _qemu = try values.decode(UTMQemuConfigurationQEMU.self, forKey: .qemu) + _input = try values.decode(UTMQemuConfigurationInput.self, forKey: .input) + _sharing = try values.decode(UTMQemuConfigurationSharing.self, forKey: .sharing) + _displays = try values.decode([UTMQemuConfigurationDisplay].self, forKey: .displays) + _drives = try values.decode([UTMQemuConfigurationDrive].self, forKey: .drives) + _networks = try values.decode([UTMQemuConfigurationNetwork].self, forKey: .networks) + _serials = try values.decode([UTMQemuConfigurationSerial].self, forKey: .serials) + _sound = try values.decode([UTMQemuConfigurationSound].self, forKey: .sound) } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(information, forKey: .information) - try container.encode(system, forKey: .system) - try container.encode(qemu, forKey: .qemu) - try container.encode(input, forKey: .input) - try container.encode(sharing, forKey: .sharing) - try container.encode(displays, forKey: .displays) - try container.encode(drives, forKey: .drives) - try container.encode(networks, forKey: .networks) - try container.encode(serials, forKey: .serials) - try container.encode(sound, forKey: .sound) + try container.encode(_information, forKey: .information) + try container.encode(_system, forKey: .system) + try container.encode(_qemu, forKey: .qemu) + try container.encode(_input, forKey: .input) + try container.encode(_sharing, forKey: .sharing) + try container.encode(_displays, forKey: .displays) + try container.encode(_drives, forKey: .drives) + try container.encode(_networks, forKey: .networks) + try container.encode(_serials, forKey: .serials) + try container.encode(_sound, forKey: .sound) try container.encode(UTMBackend.qemu, forKey: .backend) try container.encode(Self.currentVersion, forKey: .configurationVersion) } @@ -132,25 +132,129 @@ extension UTMQemuConfigurationError: LocalizedError { } } +// MARK: - Public accessors + +@MainActor extension UTMQemuConfiguration { + var information: UTMConfigurationInfo { + get { + _information + } + + set { + _information = newValue + } + } + + var system: UTMQemuConfigurationSystem { + get { + _system + } + + set { + _system = newValue + } + } + + var qemu: UTMQemuConfigurationQEMU { + get { + _qemu + } + + set { + _qemu = newValue + } + } + + var input: UTMQemuConfigurationInput { + get { + _input + } + + set { + _input = newValue + } + } + + var sharing: UTMQemuConfigurationSharing { + get { + _sharing + } + + set { + _sharing = newValue + } + } + + var displays: [UTMQemuConfigurationDisplay] { + get { + _displays + } + + set { + _displays = newValue + } + } + + var drives: [UTMQemuConfigurationDrive] { + get { + _drives + } + + set { + _drives = newValue + } + } + + var networks: [UTMQemuConfigurationNetwork] { + get { + _networks + } + + set { + _networks = newValue + } + } + + var serials: [UTMQemuConfigurationSerial] { + get { + _serials + } + + set { + _serials = newValue + } + } + + var sound: [UTMQemuConfigurationSound] { + get { + _sound + } + + set { + _sound = newValue + } + } +} + // MARK: - Defaults extension UTMQemuConfiguration { - func reset(all: Bool = true) { + private func reset(all: Bool = true) { if all { - information = .init() - system = .init() - drives = [] - } - qemu = .init() - input = .init() - sharing = .init() - displays = [] - networks = [] - serials = [] - sound = [] + _information = .init() + _system = .init() + _drives = [] + } + _qemu = .init() + _input = .init() + _sharing = .init() + _displays = [] + _networks = [] + _serials = [] + _sound = [] } - func reset(forArchitecture architecture: QEMUArchitecture, target: any QEMUTarget) { + @MainActor func reset(forArchitecture architecture: QEMUArchitecture, target: any QEMUTarget) { reset(all: false) qemu = .init(forArchitecture: architecture, target: target) input = .init(forArchitecture: architecture, target: target) @@ -176,35 +280,35 @@ extension UTMQemuConfiguration { convenience init(migrating oldConfig: UTMLegacyQemuConfiguration) { self.init() isLegacy = true - information = .init(migrating: oldConfig) - system = .init(migrating: oldConfig) - qemu = .init(migrating: oldConfig) - input = .init(migrating: oldConfig) - sharing = .init(migrating: oldConfig) + _information = .init(migrating: oldConfig) + _system = .init(migrating: oldConfig) + _qemu = .init(migrating: oldConfig) + _input = .init(migrating: oldConfig) + _sharing = .init(migrating: oldConfig) if let display = UTMQemuConfigurationDisplay(migrating: oldConfig) { - displays = [display] + _displays = [display] } - drives = (0.. [URL] { var existingDataURLs = [URL]() - existingDataURLs += try await information.saveData(to: dataURL) - existingDataURLs += try await qemu.saveData(to: dataURL, for: system) + existingDataURLs += try await _information.saveData(to: dataURL) + existingDataURLs += try await _qemu.saveData(to: dataURL, for: system) for i in 0..) -> Void in vmQueue.async { #if os(macOS) && arch(arm64) - let boot = self.appleConfig.system.boot if #available(macOS 13, *), boot.operatingSystem == .macOS { let options = VZMacOSVirtualMachineStartOptions() options.startUpFromMacOSRecovery = boot.startUpFromMacOSRecovery @@ -117,12 +114,14 @@ import Virtualization try await beginAccessingResources() try await _vmStart() if #available(macOS 12, *) { - sharedDirectoriesChanged = appleConfig.$sharedDirectories.sink { [weak self] newShares in - guard let self = self else { - return - } - self.vmQueue.async { - self.updateSharedDirectories(with: newShares) + Task { @MainActor in + sharedDirectoriesChanged = appleConfig.sharedDirectoriesPublisher.sink { [weak self] newShares in + guard let self = self else { + return + } + self.vmQueue.async { + self.updateSharedDirectories(with: newShares) + } } } } @@ -269,7 +268,7 @@ import Virtualization screenshot = screenshotDelegate?.screenshot } - private func createAppleVM() throws { + @MainActor private func createAppleVM() throws { for i in appleConfig.serials.indices { let (fd, sfd, name) = try createPty() let terminalTtyHandle = FileHandle(fileDescriptor: fd, closeOnDealloc: false) @@ -306,7 +305,7 @@ import Virtualization return } changeState(.vmStarting) - try createAppleVM() + try await createAppleVM() #if os(macOS) && arch(arm64) try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in vmQueue.async { @@ -414,11 +413,9 @@ extension UTMAppleVirtualMachine: VZVirtualMachineDelegate { sharedDirectoriesChanged = nil Task { @MainActor in stopAccesingResources() - } - for i in appleConfig.serials.indices { - if let serialPort = appleConfig.serials[i].interface { - serialPort.close() - Task { @MainActor in + for i in appleConfig.serials.indices { + if let serialPort = appleConfig.serials[i].interface { + serialPort.close() appleConfig.serials[i].interface = nil appleConfig.serials[i].fileHandleForReading = nil appleConfig.serials[i].fileHandleForWriting = nil diff --git a/Managers/UTMQemuVirtualMachine.swift b/Managers/UTMQemuVirtualMachine.swift index 908a1758c..7f007a116 100644 --- a/Managers/UTMQemuVirtualMachine.swift +++ b/Managers/UTMQemuVirtualMachine.swift @@ -29,11 +29,13 @@ extension UTMQemuVirtualMachine { if let oldPath = await registryEntry.externalDrives[drive.id]?.path { system?.stopAccessingPath(oldPath) } - for i in qemuConfig.drives.indices { - if qemuConfig.drives[i].id == drive.id { - qemuConfig.drives[i].imageURL = nil + await Task { @MainActor in + for i in qemuConfig.drives.indices { + if qemuConfig.drives[i].id == drive.id { + qemuConfig.drives[i].imageURL = nil + } } - } + }.value await registryEntry.removeExternalDrive(forId: drive.id) guard let qemu = qemu, qemu.isConnected else { return @@ -63,11 +65,13 @@ extension UTMQemuVirtualMachine { } await registryEntry.updateExternalDriveRemoteBookmark(bookmark, forId: drive.id) let newUrl = url ?? URL(fileURLWithPath: path) - for i in qemuConfig.drives.indices { - if qemuConfig.drives[i].id == drive.id { - qemuConfig.drives[i].imageURL = newUrl + await Task { @MainActor in + for i in qemuConfig.drives.indices { + if qemuConfig.drives[i].id == drive.id { + qemuConfig.drives[i].imageURL = newUrl + } } - } + }.value if let qemu = qemu, qemu.isConnected { try qemu.changeMedium(forDrive: "drive\(drive.id)", path: path) } @@ -77,7 +81,7 @@ extension UTMQemuVirtualMachine { guard system != nil && qemu != nil && qemu!.isConnected else { throw UTMQemuVirtualMachineError.invalidVmState } - for drive in qemuConfig.drives { + for drive in await qemuConfig.drives { if !drive.isExternal { continue } @@ -126,14 +130,16 @@ extension UTMQemuVirtualMachine { defer { url.stopAccessingSecurityScopedResource() } - let file = try UTMRegistryEntry.File(url: url, isReadOnly: qemuConfig.sharing.isDirectoryShareReadOnly) + let file = try await UTMRegistryEntry.File(url: url, isReadOnly: qemuConfig.sharing.isDirectoryShareReadOnly) await registryEntry.setSingleSharedDirectory(file) - if qemuConfig.sharing.directoryShareMode == .webdav { + if await qemuConfig.sharing.directoryShareMode == .webdav { if let ioService = ioService { ioService.changeSharedDirectory(url) } - qemuConfig.sharing.directoryShareUrl = url - } else if qemuConfig.sharing.directoryShareMode == .virtfs { + await Task { @MainActor in + qemuConfig.sharing.directoryShareUrl = url + }.value + } else if await qemuConfig.sharing.directoryShareMode == .virtfs { let tempBookmark = try url.bookmarkData() try await changeVirtfsSharedDirectory(with: tempBookmark, isSecurityScoped: false) } @@ -148,14 +154,16 @@ extension UTMQemuVirtualMachine { throw UTMQemuVirtualMachineError.accessDriveImageFailed } await registryEntry.updateSingleSharedDirectoryRemoteBookmark(bookmark) - qemuConfig.sharing.directoryShareUrl = URL(fileURLWithPath: path) + await Task { @MainActor in + qemuConfig.sharing.directoryShareUrl = URL(fileURLWithPath: path) + }.value } func restoreSharedDirectory() async throws { guard let share = await registryEntry.sharedDirectories.first else { return } - if qemuConfig.sharing.directoryShareMode == .virtfs { + if await qemuConfig.sharing.directoryShareMode == .virtfs { if let bookmark = share.remoteBookmark { // a share bookmark was saved while QEMU was running try await changeVirtfsSharedDirectory(with: bookmark, isSecurityScoped: true) @@ -164,7 +172,7 @@ extension UTMQemuVirtualMachine { let url = try URL(resolvingPersistentBookmarkData: share.bookmark) try await changeSharedDirectory(to: url) } - } else if qemuConfig.sharing.directoryShareMode == .webdav { + } else if await qemuConfig.sharing.directoryShareMode == .webdav { if let ioService = ioService { ioService.changeSharedDirectory(share.url) } @@ -174,15 +182,11 @@ extension UTMQemuVirtualMachine { // MARK: - Registry syncing extension UTMQemuVirtualMachine { - override func updateRegistryPostSave() async throws { + @MainActor override func updateRegistryPostSave() async throws { for i in qemuConfig.drives.indices { let drive = qemuConfig.drives[i] if drive.isExternal, let url = drive.imageURL { try await changeMedium(drive, to: url) - await Task { @MainActor in - // clear temporary URL - qemuConfig.drives[i].imageURL = nil - }.value } } if let url = config.qemuConfig!.sharing.directoryShareUrl { diff --git a/Managers/UTMVirtualMachineExtension.swift b/Managers/UTMVirtualMachineExtension.swift index 99c250647..8b9c12a56 100644 --- a/Managers/UTMVirtualMachineExtension.swift +++ b/Managers/UTMVirtualMachineExtension.swift @@ -85,33 +85,33 @@ extension UTMVirtualMachine: ObservableObject { } } - func updateRegistryPostSave() async throws { + @MainActor func updateRegistryPostSave() async throws { // do nothing by default } } public extension UTMQemuVirtualMachine { - override var detailsTitleLabel: String { + @MainActor override var detailsTitleLabel: String { config.qemuConfig!.information.name } - override var detailsSubtitleLabel: String { + @MainActor override var detailsSubtitleLabel: String { self.detailsSystemTargetLabel } - override var detailsNotes: String? { + @MainActor override var detailsNotes: String? { config.qemuConfig!.information.notes } - override var detailsSystemTargetLabel: String { + @MainActor override var detailsSystemTargetLabel: String { config.qemuConfig!.system.target.prettyValue } - override var detailsSystemArchitectureLabel: String { + @MainActor override var detailsSystemArchitectureLabel: String { config.qemuConfig!.system.architecture.prettyValue } - override var detailsSystemMemoryLabel: String { + @MainActor override var detailsSystemMemoryLabel: String { let bytesInMib = Int64(1048576) return ByteCountFormatter.string(fromByteCount: Int64(config.qemuConfig!.system.memorySize) * bytesInMib, countStyle: .memory) } @@ -137,7 +137,7 @@ public extension UTMQemuVirtualMachine { /// Check if the current VM target is supported by the host @objc var isSupported: Bool { - return UTMQemuVirtualMachine.isSupported(systemArchitecture: config.qemuConfig!.system.architecture) + return UTMQemuVirtualMachine.isSupported(systemArchitecture: config.qemuConfig!._system.architecture) } } diff --git a/Platform/Shared/UTMDownloadIPSWTask.swift b/Platform/Shared/UTMDownloadIPSWTask.swift index 1c1080556..f98e10ea1 100644 --- a/Platform/Shared/UTMDownloadIPSWTask.swift +++ b/Platform/Shared/UTMDownloadIPSWTask.swift @@ -27,7 +27,7 @@ class UTMDownloadIPSWTask: UTMDownloadTask { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first! } - init(for config: UTMAppleConfiguration) { + @MainActor init(for config: UTMAppleConfiguration) { self.config = config super.init(for: config.system.boot.macRecoveryIpswURL!, named: config.information.name) } @@ -42,7 +42,9 @@ class UTMDownloadIPSWTask: UTMDownloadTask { try fileManager.removeItem(at: cacheIpsw) } try fileManager.moveItem(at: location, to: cacheIpsw) - config.system.boot.macRecoveryIpswURL = cacheIpsw + await Task { @MainActor in + config.system.boot.macRecoveryIpswURL = cacheIpsw + }.value return UTMVirtualMachine(newConfig: config, destinationURL: UTMData.defaultStorageUrl) } } diff --git a/Platform/iOS/VMDisplayHostedView.swift b/Platform/iOS/VMDisplayHostedView.swift index fdc5406c9..acb4dc850 100644 --- a/Platform/iOS/VMDisplayHostedView.swift +++ b/Platform/iOS/VMDisplayHostedView.swift @@ -32,31 +32,31 @@ struct VMDisplayHostedView: UIViewControllerRepresentable { vm.config.qemuConfig } - var qemuInputLegacy: Bool { + @MainActor var qemuInputLegacy: Bool { vmConfig.input.usbBusSupport == .disabled || vmConfig.qemu.hasPS2Controller } - var qemuDisplayUpscaler: MTLSamplerMinMagFilter { + @MainActor var qemuDisplayUpscaler: MTLSamplerMinMagFilter { vmConfig.displays[state.device!.configIndex].upscalingFilter.metalSamplerMinMagFilter } - var qemuDisplayDownscaler: MTLSamplerMinMagFilter { + @MainActor var qemuDisplayDownscaler: MTLSamplerMinMagFilter { vmConfig.displays[state.device!.configIndex].downscalingFilter.metalSamplerMinMagFilter } - var qemuDisplayIsDynamicResolution: Bool { + @MainActor var qemuDisplayIsDynamicResolution: Bool { vmConfig.displays[state.device!.configIndex].isDynamicResolution } - var qemuDisplayIsNativeResolution: Bool { + @MainActor var qemuDisplayIsNativeResolution: Bool { vmConfig.displays[state.device!.configIndex].isNativeResolution } - var qemuHasClipboardSharing: Bool { + @MainActor var qemuHasClipboardSharing: Bool { vmConfig.sharing.hasClipboardSharing } - var qemuConsoleResizeCommand: String? { + @MainActor var qemuConsoleResizeCommand: String? { vmConfig.serials[state.device!.configIndex].terminal?.resizeCommand }