diff --git a/Scripting/UTM.sdef b/Scripting/UTM.sdef index 234408ce4..8cf6bf87f 100644 --- a/Scripting/UTM.sdef +++ b/Scripting/UTM.sdef @@ -561,6 +561,10 @@ + + + @@ -708,4 +712,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Scripting/UTMScriptingConfigImpl.swift b/Scripting/UTMScriptingConfigImpl.swift index b00edca15..dc3a633cd 100644 --- a/Scripting/UTMScriptingConfigImpl.swift +++ b/Scripting/UTMScriptingConfigImpl.swift @@ -193,6 +193,10 @@ extension UTMScriptingConfigImpl { var serializedArgument: [AnyHashable: Any] = [ "argumentString": argument.string ] + // Only add fileUrls if it is not nil and contains URLs + if let fileUrls = argument.fileUrls, !fileUrls.isEmpty { + serializedArgument["fileUrls"] = fileUrls.map({ $0 as AnyHashable }) + } return serializedArgument } @@ -517,7 +521,10 @@ extension UTMScriptingConfigImpl { let additionalArguments = records.compactMap { record -> QEMUArgument? in guard let argumentString = record["argumentString"] as? String else { return nil } var argument = QEMUArgument(argumentString) - + // fileUrls are used as required resources by QEMU. + if let fileUrls = record["fileUrls"] as? [URL] { + argument.fileUrls = fileUrls + } return argument } // Update entire additional arguments with new one. diff --git a/Scripting/UTMScriptingRegistryEntryImpl.swift b/Scripting/UTMScriptingRegistryEntryImpl.swift new file mode 100644 index 000000000..8097a1173 --- /dev/null +++ b/Scripting/UTMScriptingRegistryEntryImpl.swift @@ -0,0 +1,77 @@ +// +// Copyright © 2025 naveenrajm7. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objc extension UTMScriptingVirtualMachineImpl { + @objc var registry: [URL] { + let wrapper = UTMScriptingRegistryEntryImpl(vm.registryEntry) + return wrapper.serializeRegistry() + } + + @objc func updateRegistry(_ command: NSScriptCommand) { + let newRegistry = command.evaluatedArguments?["newRegistry"] as? [URL] + withScriptCommand(command) { [self] in + guard let newRegistry = newRegistry else { + throw ScriptingError.invalidParameter + } + let wrapper = UTMScriptingRegistryEntryImpl(vm.registryEntry) + try await wrapper.updateRegistry(from: newRegistry, qemuProcess) + } + } +} + +@MainActor +class UTMScriptingRegistryEntryImpl { + private(set) var registry: UTMRegistryEntry + + init(_ registry: UTMRegistryEntry) { + self.registry = registry + } + + func serializeRegistry() -> [URL] { + return registry.sharedDirectories.compactMap { $0.url } + } + + func updateRegistry(from fileUrls: [URL], _ system: UTMQemuSystem?) async throws { + // Clear all shared directories, we add all directories here + registry.removeAllSharedDirectories() + + // Add urls to the registry + for url in fileUrls { + // Start scoped access + let isScopedAccess = url.startAccessingSecurityScopedResource() + defer { + if isScopedAccess { + url.stopAccessingSecurityScopedResource() + } + } + + // Get bookmark from UTM process + let standardBookmark = try url.bookmarkData() + let system = system ?? UTMProcess() + let (success, bookmark, path) = await system.accessData(withBookmark: standardBookmark, securityScoped: false) + guard let bookmark = bookmark, let _ = path, success else { + throw UTMQemuVirtualMachineError.accessDriveImageFailed + } + + // Store bookmark in registry + let file = UTMRegistryEntry.File(dummyFromPath: url.path, remoteBookmark: bookmark) + registry.appendSharedDirectory(file) + } + + } +} diff --git a/Scripting/UTMScriptingVirtualMachineImpl.swift b/Scripting/UTMScriptingVirtualMachineImpl.swift index 51302848f..d3b48634c 100644 --- a/Scripting/UTMScriptingVirtualMachineImpl.swift +++ b/Scripting/UTMScriptingVirtualMachineImpl.swift @@ -90,6 +90,12 @@ class UTMScriptingVirtualMachineImpl: NSObject, UTMScriptable { } } + var qemuProcess: UTMQemuSystem? { + get async { + await (vm as? UTMQemuVirtualMachine)?.system + } + } + override var objectSpecifier: NSScriptObjectSpecifier? { let appDescription = NSApplication.classDescription() as! NSScriptClassDescription return NSUniqueIDSpecifier(containerClassDescription: appDescription, diff --git a/Services/UTMQemuVirtualMachine.swift b/Services/UTMQemuVirtualMachine.swift index dd9a34253..8be0c5d4b 100644 --- a/Services/UTMQemuVirtualMachine.swift +++ b/Services/UTMQemuVirtualMachine.swift @@ -129,7 +129,8 @@ final class UTMQemuVirtualMachine: UTMSpiceVirtualMachine { private let qemuVM = QEMUVirtualMachine() - private var system: UTMQemuSystem? { + /// QEMU Process interface + var system: UTMQemuSystem? { get async { await qemuVM.launcher as? UTMQemuSystem } diff --git a/Services/UTMRegistryEntry.swift b/Services/UTMRegistryEntry.swift index 73d21f7c9..982acc682 100644 --- a/Services/UTMRegistryEntry.swift +++ b/Services/UTMRegistryEntry.swift @@ -260,6 +260,10 @@ extension UTMRegistryEntry: UTMRegistryEntryDecodable {} } } + func appendSharedDirectory(_ file: File) { + sharedDirectories.append(file) + } + func removeAllSharedDirectories() { sharedDirectories = [] } diff --git a/UTM.xcodeproj/project.pbxproj b/UTM.xcodeproj/project.pbxproj index c881e294e..6b83570cf 100644 --- a/UTM.xcodeproj/project.pbxproj +++ b/UTM.xcodeproj/project.pbxproj @@ -272,6 +272,7 @@ B3DDF57226E9BBA300CE47F0 /* AltKit in Frameworks */ = {isa = PBXBuildFile; productRef = B3DDF57126E9BBA300CE47F0 /* AltKit */; }; CD77BE422CAB51B40074ADD2 /* UTMScriptingExportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */; }; CD77BE442CB38F060074ADD2 /* UTMScriptingImportCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD77BE432CB38F060074ADD2 /* UTMScriptingImportCommand.swift */; }; + CD84C2092D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD84C2082D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift */; }; CE020BA324AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; }; CE020BA424AEDC7C00B44AB6 /* UTMData.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE020BA224AEDC7C00B44AB6 /* UTMData.swift */; }; CE020BA724AEDEF000B44AB6 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = CE020BA624AEDEF000B44AB6 /* Logging */; }; @@ -1779,6 +1780,7 @@ C8958B6D243634DA002D86B4 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; CD77BE412CAB519F0074ADD2 /* UTMScriptingExportCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingExportCommand.swift; sourceTree = ""; }; CD77BE432CB38F060074ADD2 /* UTMScriptingImportCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingImportCommand.swift; sourceTree = ""; }; + CD84C2082D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMScriptingRegistryEntryImpl.swift; sourceTree = ""; }; CE020BA224AEDC7C00B44AB6 /* UTMData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMData.swift; sourceTree = ""; }; CE020BAA24AEE00000B44AB6 /* UTMLoggingSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMLoggingSwift.swift; sourceTree = ""; }; CE020BB524B14F8400B44AB6 /* UTMVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMVirtualMachine.swift; sourceTree = ""; }; @@ -3027,6 +3029,7 @@ CEC794B9294924E300121A9F /* UTMScriptingSerialPortImpl.swift */, CE25124829BFDBA6000790AB /* UTMScriptingGuestFileImpl.swift */, CE25124629BFDB87000790AB /* UTMScriptingGuestProcessImpl.swift */, + CD84C2082D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift */, CE25124C29C55816000790AB /* UTMScriptingConfigImpl.swift */, CE25125429C80CD4000790AB /* UTMScriptingCreateCommand.swift */, CD77BE432CB38F060074ADD2 /* UTMScriptingImportCommand.swift */, @@ -3724,6 +3727,7 @@ CEBE820D26A4C8E0007AAB12 /* VMWizardSummaryView.swift in Sources */, 8401FDB2269E602000265F0D /* VMConfigAppleDriveDetailsView.swift in Sources */, 841619B428431DA5000034B2 /* UTMQemuConfigurationQEMU.swift in Sources */, + CD84C2092D3B446D00829850 /* UTMScriptingRegistryEntryImpl.swift in Sources */, CEF0307626A2B40B00667B63 /* VMWizardHardwareView.swift in Sources */, CED234EE254796E500ED0A57 /* NumberTextField.swift in Sources */, 841619B028431952000034B2 /* UTMQemuConfigurationSystem.swift in Sources */,