diff --git a/RunLinuxVM.xcodeproj/project.pbxproj b/RunLinuxVM.xcodeproj/project.pbxproj index 663b279..24e2b99 100644 --- a/RunLinuxVM.xcodeproj/project.pbxproj +++ b/RunLinuxVM.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 8F0313B227CB143B00360D7E /* Virtualization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F0313B127CB143B00360D7E /* Virtualization.framework */; }; + 50535A58285E074300335268 /* Virtualization.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8F0313B127CB143B00360D7E /* Virtualization.framework */; }; 8FF8298D27500C060037FA83 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FF8298C27500C060037FA83 /* AppDelegate.swift */; }; 8FF8298F27500C070037FA83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8FF8298E27500C070037FA83 /* Assets.xcassets */; }; 8FF8299227500C070037FA83 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8FF8299027500C070037FA83 /* MainMenu.xib */; }; @@ -18,7 +18,7 @@ 62A7A2B81952CC2F11BFE9A2 /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; 8F0313B127CB143B00360D7E /* Virtualization.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Virtualization.framework; path = System/Library/Frameworks/Virtualization.framework; sourceTree = SDKROOT; }; 8FDC7765280E2517002AA4ED /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 8FF8298927500C060037FA83 /* GUILinuxVirtualMachineSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GUILinuxVirtualMachineSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 8FF8298927500C060037FA83 /* RunLinuxVM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RunLinuxVM.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8FF8298C27500C060037FA83 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 8FF8298E27500C070037FA83 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 8FF8299127500C070037FA83 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -31,7 +31,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8F0313B227CB143B00360D7E /* Virtualization.framework in Frameworks */, + 50535A58285E074300335268 /* Virtualization.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -50,7 +50,7 @@ isa = PBXGroup; children = ( E489E24B87B74AAC6D64B249 /* README.md */, - 8FF8298B27500C060037FA83 /* GUILinuxVirtualMachineSampleApp */, + 8FF8298B27500C060037FA83 /* RunLinuxVM */, 8FF8298A27500C060037FA83 /* Products */, 8FF82999275040C80037FA83 /* Frameworks */, 7C99BD11C87A466A17F398E6 /* Configuration */, @@ -61,12 +61,12 @@ 8FF8298A27500C060037FA83 /* Products */ = { isa = PBXGroup; children = ( - 8FF8298927500C060037FA83 /* GUILinuxVirtualMachineSampleApp.app */, + 8FF8298927500C060037FA83 /* RunLinuxVM.app */, ); name = Products; sourceTree = ""; }; - 8FF8298B27500C060037FA83 /* GUILinuxVirtualMachineSampleApp */ = { + 8FF8298B27500C060037FA83 /* RunLinuxVM */ = { isa = PBXGroup; children = ( 8FDC7765280E2517002AA4ED /* Info.plist */, @@ -75,7 +75,7 @@ 8FF8299027500C070037FA83 /* MainMenu.xib */, 8FF8299327500C070037FA83 /* GUILinuxVirtualMachineSampleApp.entitlements */, ); - path = GUILinuxVirtualMachineSampleApp; + path = RunLinuxVM; sourceTree = ""; }; 8FF82999275040C80037FA83 /* Frameworks */ = { @@ -97,9 +97,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 8FF8298827500C060037FA83 /* GUILinuxVirtualMachineSampleApp */ = { + 8FF8298827500C060037FA83 /* RunLinuxVM */ = { isa = PBXNativeTarget; - buildConfigurationList = 8FF8299627500C070037FA83 /* Build configuration list for PBXNativeTarget "GUILinuxVirtualMachineSampleApp" */; + buildConfigurationList = 8FF8299627500C070037FA83 /* Build configuration list for PBXNativeTarget "RunLinuxVM" */; buildPhases = ( 8FF8298527500C060037FA83 /* Sources */, 8FF8298627500C060037FA83 /* Frameworks */, @@ -109,9 +109,11 @@ ); dependencies = ( ); - name = GUILinuxVirtualMachineSampleApp; + name = RunLinuxVM; + packageProductDependencies = ( + ); productName = GUILinuxVirtualMachineSampleApp; - productReference = 8FF8298927500C060037FA83 /* GUILinuxVirtualMachineSampleApp.app */; + productReference = 8FF8298927500C060037FA83 /* RunLinuxVM.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -130,7 +132,7 @@ }; }; }; - buildConfigurationList = 8FF8298427500C060037FA83 /* Build configuration list for PBXProject "GUILinuxVirtualMachineSampleApp" */; + buildConfigurationList = 8FF8298427500C060037FA83 /* Build configuration list for PBXProject "RunLinuxVM" */; compatibilityVersion = "Xcode 13.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -139,11 +141,13 @@ Base, ); mainGroup = 8FF8298027500C060037FA83; + packageReferences = ( + ); productRefGroup = 8FF8298A27500C060037FA83 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - 8FF8298827500C060037FA83 /* GUILinuxVirtualMachineSampleApp */, + 8FF8298827500C060037FA83 /* RunLinuxVM */, ); }; /* End PBXProject section */ @@ -244,7 +248,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; + SDKROOT = macosx13.0; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -304,7 +308,8 @@ MACOSX_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - SDKROOT = macosx; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx13.0; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; @@ -316,7 +321,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "$(PROJECT_DIR)/GUILinuxVirtualMachineSampleApp/GUILinuxVirtualMachineSampleApp.entitlements"; + CODE_SIGN_ENTITLEMENTS = "$(PROJECT_DIR)/RunLinuxVM/GUILinuxVirtualMachineSampleApp.entitlements"; CODE_SIGN_IDENTITY = "Mac Developer"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; @@ -327,7 +332,9 @@ DEVELOPMENT_TEAM = 96775H948U; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "$(PROJECT_DIR)/GUILinuxVirtualMachineSampleApp/Info.plist"; + INFOPLIST_FILE = "$(PROJECT_DIR)/RunLinuxVM/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = RunLinuxVM; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSPrincipalClass = NSApplication; @@ -337,10 +344,11 @@ ); MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.GUILinuxVirtualMachineSampleApp${SAMPLE_CODE_DISAMBIGUATOR}"; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.RunLinuxVM${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; + SDKROOT = macosx13.0; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; @@ -352,9 +360,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = "$(PROJECT_DIR)/GUILinuxVirtualMachineSampleApp/GUILinuxVirtualMachineSampleApp.entitlements"; - CODE_SIGN_IDENTITY = "Mac Developer"; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; + CODE_SIGN_ENTITLEMENTS = "$(PROJECT_DIR)/RunLinuxVM/GUILinuxVirtualMachineSampleApp.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development: Supphachoke Suntiwichaya (373VAZTPQ2)"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -363,7 +371,9 @@ DEVELOPMENT_TEAM = 96775H948U; ENABLE_HARDENED_RUNTIME = NO; GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "$(PROJECT_DIR)/GUILinuxVirtualMachineSampleApp/Info.plist"; + INFOPLIST_FILE = "$(PROJECT_DIR)/RunLinuxVM/Info.plist"; + INFOPLIST_KEY_CFBundleDisplayName = RunLinuxVM; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INFOPLIST_KEY_NSMainNibFile = MainMenu; INFOPLIST_KEY_NSPrincipalClass = NSApplication; @@ -373,10 +383,11 @@ ); MACOSX_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.GUILinuxVirtualMachineSampleApp${SAMPLE_CODE_DISAMBIGUATOR}"; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = "com.example.apple-samplecode.RunLinuxVM${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = macosx; + SDKROOT = macosx13.0; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; }; @@ -385,7 +396,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 8FF8298427500C060037FA83 /* Build configuration list for PBXProject "GUILinuxVirtualMachineSampleApp" */ = { + 8FF8298427500C060037FA83 /* Build configuration list for PBXProject "RunLinuxVM" */ = { isa = XCConfigurationList; buildConfigurations = ( 8FF8299427500C070037FA83 /* Debug */, @@ -394,7 +405,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 8FF8299627500C070037FA83 /* Build configuration list for PBXNativeTarget "GUILinuxVirtualMachineSampleApp" */ = { + 8FF8299627500C070037FA83 /* Build configuration list for PBXNativeTarget "RunLinuxVM" */ = { isa = XCConfigurationList; buildConfigurations = ( 8FF8299727500C070037FA83 /* Debug */, diff --git a/RunLinuxVM.xcodeproj/xcshareddata/xcschemes/GUILinuxVirtualMachineSampleApp.xcscheme b/RunLinuxVM.xcodeproj/xcshareddata/xcschemes/GUILinuxVirtualMachineSampleApp.xcscheme new file mode 100644 index 0000000..c860a0a --- /dev/null +++ b/RunLinuxVM.xcodeproj/xcshareddata/xcschemes/GUILinuxVirtualMachineSampleApp.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RunLinuxVM/AppDelegate.swift b/RunLinuxVM/AppDelegate.swift index 4d303a7..54fad75 100644 --- a/RunLinuxVM/AppDelegate.swift +++ b/RunLinuxVM/AppDelegate.swift @@ -1,34 +1,132 @@ /* -See LICENSE folder for this sample’s licensing information. - -Abstract: -The app delegate that sets up and starts the virtual machine. -*/ + See LICENSE folder for this sample’s licensing information. + + Abstract: + The app delegate that sets up and starts the virtual machine. + */ import Virtualization -let vmBundlePath = NSHomeDirectory() + "/LinuxVM.bundle/" + +var vmBundlePath = NSHomeDirectory() + "/LinuxVM.bundle/" let mainDiskImagePath = vmBundlePath + "Disk.img" let efiVariableStorePath = vmBundlePath + "NVRAM" let machineIdentifierPath = vmBundlePath + "MachineIdentifier" +var cpuNums: Int = 0 +var screenWidth = 1920 +var screenHeight = 1080 +var imageSize: UInt64 = 10 +var ramSize: UInt64 = 4 + +struct Resolution { + var width: Int + var height: Int +} + +let Resolutions: [String: Resolution] = [ + "hd": Resolution(width: 1280, height: 720), + "fhd": Resolution(width: 1920, height: 1080), + "2k": Resolution(width: 2560, height: 1440), + "4k": Resolution(width: 4096, height: 2160) +] + +enum OptionCode: Int32 { + case c = 0x63 + case d = 0x64 + case h = 0x68 + case m = 0x6D + case r = 0x72 + case p = 0x70 + case firstLongOption = 0x100 +} + +extension StaticString { + var ccharPointer: UnsafePointer { + let rawPointer = UnsafeRawPointer(utf8Start) + return rawPointer.bindMemory(to: CChar.self, capacity: utf8CodeUnitCount) + } +} + +let longOpts: [option] = [ + option(name: ("cpu" as StaticString).ccharPointer, has_arg: required_argument, flag: nil, val: OptionCode.c.rawValue), + option(name: ("disk" as StaticString).ccharPointer, has_arg: required_argument, flag: nil, val: OptionCode.d.rawValue), + option(name: ("help" as StaticString).ccharPointer, has_arg: no_argument, flag: nil, val: OptionCode.h.rawValue), + option(name: ("resolution" as StaticString).ccharPointer, has_arg: required_argument, flag: nil, val: OptionCode.r.rawValue), + option(name: ("mem" as StaticString).ccharPointer, has_arg: required_argument, flag: nil, val: OptionCode.m.rawValue), + option(name: ("path" as StaticString).ccharPointer, has_arg: required_argument, flag: nil, val: OptionCode.p.rawValue), + option() +] + + @main class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { - + @IBOutlet var window: NSWindow! - + @IBOutlet weak var virtualMachineView: VZVirtualMachineView! - + private var virtualMachine: VZVirtualMachine! - + private var installerISOPath: URL? - + private var needsInstall = true - + override init() { + + while case let opt = getopt_long(CommandLine.argc, CommandLine.unsafeArgv, "hc:d:r:m:p:", longOpts, nil), opt != -1 { + switch opt { + + case OptionCode.c.rawValue: + let cpuarg = (Int(String(cString: optarg)) ?? 1) + + if cpuarg > 0 && cpuarg < ProcessInfo.processInfo.processorCount { + cpuNums = cpuarg + } else { + fatalError("CPU number [1..\(ProcessInfo.processInfo.processorCount - 1)]") + } + + case OptionCode.d.rawValue: + imageSize = UInt64(Int(String(cString: optarg)) ?? 0) + + case OptionCode.m.rawValue: + ramSize = UInt64(Int(String(cString: optarg)) ?? 0) + + case OptionCode.p.rawValue: + vmBundlePath = String(cString: optarg) + + case OptionCode.r.rawValue: + let optres = String(cString: optarg) + + if let res = Resolutions[optres]{ + screenWidth = res.width + screenHeight = res.height + } else { + fatalError("Invalid resolution \(optres): hd, fhd, 2k and 4k") + } + + + case OptionCode.h.rawValue: + + print(""" + Options available: + --cpu, -c: number of cpus [1..\(ProcessInfo.processInfo.processorCount - 1)] + --disk, -d: disk image size in GB + --mem, -m: memory size in GB + --path, -p: bundle path with tailing slash eg. /path/to/Debian.bundle/ + --resolution, -r: screen resolution preset [hd, fhd, 2k, 4k] + --help, -h: show this help + """) + exit(0) + + default: + print("") + } + } + super.init() } - + private func createVMBundle() { do { try FileManager.default.createDirectory(atPath: vmBundlePath, withIntermediateDirectories: false) @@ -36,159 +134,181 @@ class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { fatalError("Failed to create “GUI Linux VM.bundle.”") } } - + // Create an empty disk image for the virtual machine. private func createMainDiskImage() { let diskCreated = FileManager.default.createFile(atPath: mainDiskImagePath, contents: nil, attributes: nil) if !diskCreated { fatalError("Failed to create the main disk image.") } - + guard let mainDiskFileHandle = try? FileHandle(forWritingTo: URL(fileURLWithPath: mainDiskImagePath)) else { fatalError("Failed to get the file handle for the main disk image.") } - + do { - // 64 GB disk space. - try mainDiskFileHandle.truncate(atOffset: 64 * 1024 * 1024 * 1024) + + try mainDiskFileHandle.truncate(atOffset: imageSize * 1024 * 1024 * 1024) } catch { fatalError("Failed to truncate the main disk image.") } } - + // MARK: Create device configuration objects for the virtual machine. - + private func createBlockDeviceConfiguration() -> VZVirtioBlockDeviceConfiguration { guard let mainDiskAttachment = try? VZDiskImageStorageDeviceAttachment(url: URL(fileURLWithPath: mainDiskImagePath), readOnly: false) else { fatalError("Failed to create main disk attachment.") } - + let mainDisk = VZVirtioBlockDeviceConfiguration(attachment: mainDiskAttachment) return mainDisk } - + private func computeCPUCount() -> Int { let totalAvailableCPUs = ProcessInfo.processInfo.processorCount - - var virtualCPUCount = totalAvailableCPUs <= 1 ? 1 : totalAvailableCPUs - 1 + + var virtualCPUCount = totalAvailableCPUs <= 1 ? 1 : cpuNums > 0 ? cpuNums : totalAvailableCPUs - 1 virtualCPUCount = max(virtualCPUCount, VZVirtualMachineConfiguration.minimumAllowedCPUCount) virtualCPUCount = min(virtualCPUCount, VZVirtualMachineConfiguration.maximumAllowedCPUCount) - + return virtualCPUCount } - + private func computeMemorySize() -> UInt64 { - var memorySize = (4 * 1024 * 1024 * 1024) as UInt64 // 4 GiB + var memorySize = (ramSize * 1024 * 1024 * 1024) as UInt64 memorySize = max(memorySize, VZVirtualMachineConfiguration.minimumAllowedMemorySize) memorySize = min(memorySize, VZVirtualMachineConfiguration.maximumAllowedMemorySize) - + return memorySize } - + private func createAndSaveMachineIdentifier() -> VZGenericMachineIdentifier { let machineIdentifier = VZGenericMachineIdentifier() - + // Store the machine identifier to disk so you can retrieve it for subsequent boots. try! machineIdentifier.dataRepresentation.write(to: URL(fileURLWithPath: machineIdentifierPath)) return machineIdentifier } - + private func retrieveMachineIdentifier() -> VZGenericMachineIdentifier { // Retrieve the machine identifier. guard let machineIdentifierData = try? Data(contentsOf: URL(fileURLWithPath: machineIdentifierPath)) else { fatalError("Failed to retrieve the machine identifier data.") } - + guard let machineIdentifier = VZGenericMachineIdentifier(dataRepresentation: machineIdentifierData) else { fatalError("Failed to create the machine identifier.") } - + return machineIdentifier } - + private func createEFIVariableStore() -> VZEFIVariableStore { guard let efiVariableStore = try? VZEFIVariableStore(creatingVariableStoreAt: URL(fileURLWithPath: efiVariableStorePath)) else { fatalError("Failed to create the EFI variable store.") } - + return efiVariableStore } - + private func retrieveEFIVariableStore() -> VZEFIVariableStore { if !FileManager.default.fileExists(atPath: efiVariableStorePath) { fatalError("EFI variable store does not exist.") } - + return VZEFIVariableStore(url: URL(fileURLWithPath: efiVariableStorePath)) } - + private func createUSBMassStorageDeviceConfiguration() -> VZUSBMassStorageDeviceConfiguration { guard let intallerDiskAttachment = try? VZDiskImageStorageDeviceAttachment(url: installerISOPath!, readOnly: true) else { fatalError("Failed to create installer's disk attachment.") } - + return VZUSBMassStorageDeviceConfiguration(attachment: intallerDiskAttachment) } - + private func createNetworkDeviceConfiguration() -> VZVirtioNetworkDeviceConfiguration { let networkDevice = VZVirtioNetworkDeviceConfiguration() networkDevice.attachment = VZNATNetworkDeviceAttachment() - + return networkDevice } - + private func createGraphicsDeviceConfiguration() -> VZVirtioGraphicsDeviceConfiguration { let graphicsDevice = VZVirtioGraphicsDeviceConfiguration() graphicsDevice.scanouts = [ - VZVirtioGraphicsScanoutConfiguration(widthInPixels: 1280, heightInPixels: 720) + VZVirtioGraphicsScanoutConfiguration(widthInPixels: screenWidth, heightInPixels: screenHeight) ] - + return graphicsDevice } - + private func createInputAudioDeviceConfiguration() -> VZVirtioSoundDeviceConfiguration { let inputAudioDevice = VZVirtioSoundDeviceConfiguration() - + let inputStream = VZVirtioSoundDeviceInputStreamConfiguration() inputStream.source = VZHostAudioInputStreamSource() - + inputAudioDevice.streams = [inputStream] return inputAudioDevice } - + private func createOutputAudioDeviceConfiguration() -> VZVirtioSoundDeviceConfiguration { let outputAudioDevice = VZVirtioSoundDeviceConfiguration() - + let outputStream = VZVirtioSoundDeviceOutputStreamConfiguration() outputStream.sink = VZHostAudioOutputStreamSink() - + outputAudioDevice.streams = [outputStream] return outputAudioDevice } - + private func createSpiceAgentConsoleDeviceConfiguration() -> VZVirtioConsoleDeviceConfiguration { let consoleDevice = VZVirtioConsoleDeviceConfiguration() - + let spiceAgentPort = VZVirtioConsolePortConfiguration() spiceAgentPort.name = VZSpiceAgentPortAttachment.spiceAgentPortName spiceAgentPort.attachment = VZSpiceAgentPortAttachment() consoleDevice.ports[0] = spiceAgentPort - + return consoleDevice } - + + + private func createRosettaShare(configuration: VZVirtualMachineConfiguration) throws { + + let tag = "ROSETTA" + // let configuration = VZVirtualMachineConfiguration() + do { + let _ = try VZVirtioFileSystemDeviceConfiguration.validateTag(tag) + let rosettaDirectoryShare = try VZLinuxRosettaDirectoryShare() + let fileSystemDevice = VZVirtioFileSystemDeviceConfiguration(tag: tag) + fileSystemDevice.share = rosettaDirectoryShare + + configuration.directorySharingDevices = [ fileSystemDevice ] + } catch { + fatalError("Rosetta is unavailable") + + } + + + } + // MARK: Create the virtual machine configuration and instantiate the virtual machine. - + func createVirtualMachine() { + + let virtualMachineConfiguration = VZVirtualMachineConfiguration() - + virtualMachineConfiguration.cpuCount = computeCPUCount() virtualMachineConfiguration.memorySize = computeMemorySize() - + let platform = VZGenericPlatformConfiguration() let bootloader = VZEFIBootLoader() let disksArray = NSMutableArray() - + if needsInstall { // This is a fresh install: Create a new machine identifier and EFI variable store, // and configure a USB mass storage device to boot the ISO image. @@ -202,30 +322,35 @@ class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { platform.machineIdentifier = retrieveMachineIdentifier() bootloader.variableStore = retrieveEFIVariableStore() } - + virtualMachineConfiguration.platform = platform virtualMachineConfiguration.bootLoader = bootloader - + disksArray.add(createBlockDeviceConfiguration()) guard let disks = disksArray as? [VZStorageDeviceConfiguration] else { fatalError("Invalid disksArray.") } virtualMachineConfiguration.storageDevices = disks - + virtualMachineConfiguration.networkDevices = [createNetworkDeviceConfiguration()] virtualMachineConfiguration.graphicsDevices = [createGraphicsDeviceConfiguration()] virtualMachineConfiguration.audioDevices = [createInputAudioDeviceConfiguration(), createOutputAudioDeviceConfiguration()] - + virtualMachineConfiguration.keyboards = [VZUSBKeyboardConfiguration()] virtualMachineConfiguration.pointingDevices = [VZUSBScreenCoordinatePointingDeviceConfiguration()] - virtualMachineConfiguration.consoleDevices = [createSpiceAgentConsoleDeviceConfiguration()] - + virtualMachineConfiguration.consoleDevices = [createSpiceAgentConsoleDeviceConfiguration()] + do { + try createRosettaShare(configuration: virtualMachineConfiguration) + } catch { + print("Rosetta is unavailable\n") + } + try! virtualMachineConfiguration.validate() virtualMachine = VZVirtualMachine(configuration: virtualMachineConfiguration) } - + // MARK: Start the virtual machine. - + func configureAndStartVirtualMachine() { DispatchQueue.main.async { self.createVirtualMachine() @@ -235,17 +360,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { switch result { case let .failure(error): fatalError("Virtual machine failed to start with error: \(error)") - + default: print("Virtual machine successfully started.") } }) } } - + func applicationDidFinishLaunching(_ aNotification: Notification) { NSApp.activate(ignoringOtherApps: true) - + // If "GUI Linux VM.bundle" doesn't exist, the sample app tries to create // one and install Linux onto an empty disk image from the ISO image, // otherwise, it tries to directly boot from the disk image inside @@ -254,13 +379,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { needsInstall = true createVMBundle() createMainDiskImage() - + let openPanel = NSOpenPanel() openPanel.canChooseFiles = true openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = false openPanel.canCreateDirectories = false - + openPanel.begin { (result) -> Void in if result == .OK { self.installerISOPath = openPanel.url! @@ -274,24 +399,26 @@ class AppDelegate: NSObject, NSApplicationDelegate, VZVirtualMachineDelegate { configureAndStartVirtualMachine() } } - + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } - + // MARK: VZVirtualMachineDelegate methods. - + func virtualMachine(_ virtualMachine: VZVirtualMachine, didStopWithError error: Error) { print("Virtual machine did stop with error: \(error.localizedDescription)") exit(-1) } - + func guestDidStop(_ virtualMachine: VZVirtualMachine) { print("Guest did stop virtual machine.") exit(0) } - + func virtualMachine(_ virtualMachine: VZVirtualMachine, networkDevice: VZNetworkDevice, attachmentWasDisconnectedWithError error: Error) { print("Netowrk attachment was disconnected with error: \(error.localizedDescription)") } } + + diff --git a/RunLinuxVM/Base.lproj/MainMenu.xib b/RunLinuxVM/Base.lproj/MainMenu.xib index fa1a858..0eb6450 100644 --- a/RunLinuxVM/Base.lproj/MainMenu.xib +++ b/RunLinuxVM/Base.lproj/MainMenu.xib @@ -12,7 +12,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/RunLinuxVM/appstore.png b/RunLinuxVM/appstore.png new file mode 100644 index 0000000..3b692f9 Binary files /dev/null and b/RunLinuxVM/appstore.png differ diff --git a/RunLinuxVM/playstore.png b/RunLinuxVM/playstore.png new file mode 100644 index 0000000..da43753 Binary files /dev/null and b/RunLinuxVM/playstore.png differ