diff --git a/virtualOS.xcodeproj/project.pbxproj b/virtualOS.xcodeproj/project.pbxproj index 159abc7..cf8fa44 100644 --- a/virtualOS.xcodeproj/project.pbxproj +++ b/virtualOS.xcodeproj/project.pbxproj @@ -8,9 +8,10 @@ /* Begin PBXBuildFile section */ 0005A77A27E2809E0013BE83 /* VirtualMachineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0005A77927E2809E0013BE83 /* VirtualMachineView.swift */; }; + 002E64922871B2DD00CE95A0 /* UserDefaults+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 002E64912871B2DD00CE95A0 /* UserDefaults+Settings.swift */; }; 0044A65527F601E60007988A /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0044A65427F601E60007988A /* MainViewModel.swift */; }; 0044A65A27F76BD30007988A /* URL+Paths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0044A65927F76BD30007988A /* URL+Paths.swift */; }; - 006504E727F9D59300723BCA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006504E627F9D59300723BCA /* SettingsView.swift */; }; + 006504E727F9D59300723BCA /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 006504E627F9D59300723BCA /* ConfigurationView.swift */; }; 007987AF27E2487200960D74 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 007987AE27E2487200960D74 /* LICENSE */; }; 007987B127E24A8400960D74 /* VirtualMac.swift in Sources */ = {isa = PBXBuildFile; fileRef = 007987B027E24A8400960D74 /* VirtualMac.swift */; }; 0090AF6127E25F6F0077D35F /* UInt64+Byte.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0090AF6027E25F6F0077D35F /* UInt64+Byte.swift */; }; @@ -22,6 +23,7 @@ 00989C8227E2340D0048776B /* virtualOSUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C8127E2340D0048776B /* virtualOSUITestsLaunchTests.swift */; }; 00989C9627E236A10048776B /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C9427E236A10048776B /* MainView.swift */; }; 00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C9927E238930048776B /* VirtualMacConfiguration.swift */; }; + 00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -43,9 +45,10 @@ /* Begin PBXFileReference section */ 0005A77927E2809E0013BE83 /* VirtualMachineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMachineView.swift; sourceTree = ""; }; + 002E64912871B2DD00CE95A0 /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = ""; }; 0044A65427F601E60007988A /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; }; 0044A65927F76BD30007988A /* URL+Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Paths.swift"; sourceTree = ""; }; - 006504E627F9D59300723BCA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + 006504E627F9D59300723BCA /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = ""; }; 007987AE27E2487200960D74 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 007987B027E24A8400960D74 /* VirtualMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMac.swift; sourceTree = ""; }; 0090AF6027E25F6F0077D35F /* UInt64+Byte.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UInt64+Byte.swift"; sourceTree = ""; }; @@ -61,6 +64,7 @@ 00989C8127E2340D0048776B /* virtualOSUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = virtualOSUITestsLaunchTests.swift; sourceTree = ""; }; 00989C9427E236A10048776B /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 00989C9927E238930048776B /* VirtualMacConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMacConfiguration.swift; sourceTree = ""; }; + 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 00BA26AC2826DAF200E80B76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ @@ -94,6 +98,7 @@ children = ( 0090AF6027E25F6F0077D35F /* UInt64+Byte.swift */, 0044A65927F76BD30007988A /* URL+Paths.swift */, + 002E64912871B2DD00CE95A0 /* UserDefaults+Settings.swift */, ); path = Extension; sourceTree = ""; @@ -174,7 +179,8 @@ children = ( 00989C9427E236A10048776B /* MainView.swift */, 0005A77927E2809E0013BE83 /* VirtualMachineView.swift */, - 006504E627F9D59300723BCA /* SettingsView.swift */, + 006504E627F9D59300723BCA /* ConfigurationView.swift */, + 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */, ); path = View; sourceTree = ""; @@ -243,7 +249,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1330; - LastUpgradeCheck = 1330; + LastUpgradeCheck = 1400; TargetAttributes = { 00989C5F27E2340C0048776B = { CreatedOnToolsVersion = 13.3; @@ -310,12 +316,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 002E64922871B2DD00CE95A0 /* UserDefaults+Settings.swift in Sources */, 00989C9627E236A10048776B /* MainView.swift in Sources */, 0005A77A27E2809E0013BE83 /* VirtualMachineView.swift in Sources */, 00989C6427E2340C0048776B /* virtualOSApp.swift in Sources */, 007987B127E24A8400960D74 /* VirtualMac.swift in Sources */, 0044A65A27F76BD30007988A /* URL+Paths.swift in Sources */, - 006504E727F9D59300723BCA /* SettingsView.swift in Sources */, + 00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */, + 006504E727F9D59300723BCA /* ConfigurationView.swift in Sources */, 00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */, 0090AF6127E25F6F0077D35F /* UInt64+Byte.swift in Sources */, 0044A65527F601E60007988A /* MainViewModel.swift in Sources */, @@ -388,6 +396,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -448,6 +457,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -474,23 +484,26 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = virtualOS/virtualOS.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\""; DEVELOPMENT_TEAM = 2AD47BTDQ6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = virtualOS/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -504,23 +517,26 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = virtualOS/virtualOS.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\""; DEVELOPMENT_TEAM = 2AD47BTDQ6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = virtualOS/Info.plist; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.business"; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.1; PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -535,6 +551,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AD47BTDQ6; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 12.3; @@ -554,6 +571,7 @@ BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AD47BTDQ6; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 12.3; @@ -572,6 +590,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AD47BTDQ6; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; @@ -589,6 +608,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 2AD47BTDQ6; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; diff --git a/virtualOS.xcodeproj/xcshareddata/xcschemes/virtualOS.xcscheme b/virtualOS.xcodeproj/xcshareddata/xcschemes/virtualOS.xcscheme index 9262224..138a0b2 100644 --- a/virtualOS.xcodeproj/xcshareddata/xcschemes/virtualOS.xcscheme +++ b/virtualOS.xcodeproj/xcshareddata/xcschemes/virtualOS.xcscheme @@ -1,6 +1,6 @@ ) in + + var restoreImageURL = URL.restoreImageURL + if let customRestoreImageURL = customRestoreImageURL { + restoreImageURL = customRestoreImageURL + } + + VZMacOSRestoreImage.load(from: restoreImageURL) { (result: Result) in switch result { case .success(let restoreImage): self.didLoad(restoreImage: restoreImage, completionHandler: completionHandler) - case .failure(_): - completionHandler("Error: failure reading restore image") + case .failure(let failure): + completionHandler("Error: Could not read restore image: \(failure)") } } } - func loadAndInstallRestoreImage(delegate: VZVirtualMachineDelegate, progressHandler: @escaping ProgressHandler, completionHandler: @escaping InstallCompletionHander) { - VZMacOSRestoreImage.load(from: URL.restoreImageURL) { (result: Result) in + func loadAndInstallRestoreImage(delegate: VZVirtualMachineDelegate, customRestoreImageURL: URL?, progressHandler: @escaping ProgressHandler, completionHandler: @escaping InstallCompletionHander) { + var restoreImageURL = URL.restoreImageURL + if let customRestoreImageURL = customRestoreImageURL { + restoreImageURL = customRestoreImageURL + } + + VZMacOSRestoreImage.load(from: restoreImageURL) { (result: Result) in switch result { case .success(let restoreImage): if let errorString = self.restore(from: restoreImage) { completionHandler(errorString, nil) } else if let virtualMachineConfiguration = self.virtualMachineConfiguration { - self.startInstall(ipswURL: URL.restoreImageURL, virtualMacConfiguration: virtualMachineConfiguration, delegate: delegate, progressHandler: progressHandler, completionHandler: completionHandler) + self.startInstall(ipswURL: restoreImageURL, virtualMacConfiguration: virtualMachineConfiguration, delegate: delegate, progressHandler: progressHandler, completionHandler: completionHandler) } else { completionHandler("Error: No virtual machine configuration found", nil) } @@ -124,19 +134,19 @@ final class VirtualMac: ObservableObject { try virtualMacConfiguration.validate() self.virtualMachineConfiguration = virtualMacConfiguration } catch (let error) { - debugLog("Error: \(error.localizedDescription)") + virtualOSApp.debugLog("Error: \(error.localizedDescription)") return nil } if let errorString = writeParametersToDisk() { - debugLog(errorString) + virtualOSApp.debugLog(errorString) return nil } let virtualMachine = VZVirtualMachine(configuration: virtualMacConfiguration, queue: .main) virtualMachine.delegate = delegate - debugLog("Using \(virtualMacConfiguration.cpuCount) cores, \(virtualMacConfiguration.memorySize.bytesToGigabytes()) GB RAM and screen size \(parameters.screenWidth)x\(parameters.screenHeight) px at \(parameters.pixelsPerInch) ppi") + virtualOSApp.debugLog("Using \(virtualMacConfiguration.cpuCount) cores, \(virtualMacConfiguration.memorySize.bytesToGigabytes()) GB RAM and screen size \(parameters.screenWidth)x\(parameters.screenHeight) px at \(parameters.pixelsPerInch) ppi") return virtualMachine } @@ -144,10 +154,10 @@ final class VirtualMac: ObservableObject { func stop(virtualMachine: VZVirtualMachine, completionHandler: @escaping InstallCompletionHander) { virtualMachine.stop(completionHandler: { (error: Error?) in if let error = error { - debugLog("Error while stopping: \(error)") + virtualOSApp.debugLog("Error while stopping: \(error)") completionHandler(error.localizedDescription, nil) } else { - debugLog("Stopped") + virtualOSApp.debugLog("Stopped") completionHandler(nil, virtualMachine) // nil: no error } }) @@ -186,7 +196,7 @@ final class VirtualMac: ObservableObject { } fileprivate func fetchLatestSupportedRestoreImage(progressHandler: @escaping ProgressHandler, completionHandler: @escaping CompletionHander) { - debugLog("Attempting to download latest available restore image") + virtualOSApp.debugLog("Attempting to download latest available restore image") VZMacOSRestoreImage.fetchLatestSupported { [self](result: Result) in switch result { case let .failure(error): @@ -232,7 +242,7 @@ final class VirtualMac: ObservableObject { virtualMachineConfiguration = VirtualMacConfiguration() virtualMachineConfiguration?.setDefault(parameters: ¶meters) - debugLog("Parameters from restore image: \(parameters)") + virtualOSApp.debugLog("Parameters from restore image: \(parameters)") if let errorString = writeParametersToDisk() { completionHandler(errorString) @@ -268,7 +278,7 @@ final class VirtualMac: ObservableObject { fileprivate func readSupportedConfiguration(from restoreImage: VZMacOSRestoreImage) -> (VZMacOSConfigurationRequirements?, String?) { let version = restoreImage.operatingSystemVersion versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion) (Build \(restoreImage.buildVersion))" - debugLog("Restore image operating system version: \(versionString)") + virtualOSApp.debugLog("Restore image operating system version: \(versionString)") guard let mostFeaturefulSupportedConfiguration = restoreImage.mostFeaturefulSupportedConfiguration else { return (nil, "Error: Restore image for macOS version \(versionString) is not supported on this machine") @@ -296,10 +306,11 @@ final class VirtualMac: ObservableObject { installer.install { result in switch result { case .success: - debugLog("Install finished") + virtualOSApp.debugLog("Install finished") self.stop(virtualMachine: virtualMachine, completionHandler: completionHandler) case .failure(let error): - completionHandler("Error: Install failed: \(error)", nil) + self.progressObserverCancellable?.cancel() + completionHandler("Error: Install failed: \(error).\nPlease select `Delete Virtual Machine` and `Delete Restore Image` from the file menu or use a different restore image and try again.", nil) } } @@ -319,14 +330,14 @@ final class VirtualMac: ObservableObject { let bundleFileDescriptor = mkdir(URL.vmBundlePath, S_IRWXU | S_IRWXG | S_IRWXO) if bundleFileDescriptor == -1 { if errno == EEXIST { - return "Error: Failed to create VM bundle: the base directory already exists" + return "Failed to create VM bundle: the base directory already exists" } - return "Error: Failed to create VM bundle" + return "Failed to create VM bundle at \(URL.vmBundlePath) (error number \(errno))" } let result = close(bundleFileDescriptor) if result != 0 { - debugLog("Error: Failed to close VM bundle (\(result))") + virtualOSApp.debugLog("Failed to close VM bundle (\(result))") } return nil // no error diff --git a/virtualOS/Model/VirtualMacConfiguration.swift b/virtualOS/Model/VirtualMacConfiguration.swift index 4ded5e4..b54955a 100644 --- a/virtualOS/Model/VirtualMacConfiguration.swift +++ b/virtualOS/Model/VirtualMacConfiguration.swift @@ -23,12 +23,12 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { func readFromDisk(using parameters: inout VirtualMac.Parameters) { let (errorString, platform) = readPlaformFromDisk() if let errorString = errorString { - debugLog(errorString) + virtualOSApp.debugLog(errorString) } else if let platform = platform { self.platform = platform configure(with: ¶meters) } else { - debugLog("Error: Reading platform from disk failed") + virtualOSApp.debugLog("Error: Reading platform from disk failed") } } @@ -73,7 +73,7 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { options: [.allowOverwrite] ) } catch { - debugLog("Error: could not create auxiliary storage device") + virtualOSApp.debugLog("Error: could not create auxiliary storage device") return false } @@ -81,7 +81,7 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { try platformConfiguration.hardwareModel.dataRepresentation.write(to: URL.hardwareModelURL) try platformConfiguration.machineIdentifier.dataRepresentation.write(to: URL.machineIdentifierURL) } catch { - debugLog("Error: could store platform information to disk") + virtualOSApp.debugLog("Error: could store platform information to disk") return false } @@ -101,7 +101,7 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { if parameters.microphoneEnabled { AVCaptureDevice.requestAccess(for: .audio) { (granted: Bool) in - debugLog("Microphone request granted: \(granted)") + virtualOSApp.debugLog("Microphone request granted: \(granted)") } let inputStreamConfiguration = VZVirtioSoundDeviceInputStreamConfiguration() @@ -131,7 +131,7 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { let blockDeviceConfiguration = VZVirtioBlockDeviceConfiguration(attachment: diskImageStorageDeviceAttachment) storageDevices = [blockDeviceConfiguration] } else { - debugLog("Error: could not create storage device") + virtualOSApp.debugLog("Error: could not create storage device") } } @@ -168,7 +168,6 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration { } macPlatform.hardwareModel = hardwareModel - // Retrieve the machine identifier; you should save this value to disk during installation. guard let machineIdentifierData = try? Data(contentsOf: URL.machineIdentifierURL) else { return ("Error: Failed to retrieve machine identifier data.", nil) } diff --git a/virtualOS/View/MainView.swift b/virtualOS/View/MainView.swift index 23e2094..6ebf38b 100644 --- a/virtualOS/View/MainView.swift +++ b/virtualOS/View/MainView.swift @@ -25,8 +25,19 @@ struct MainView: View { }.disabled(viewModel.buttonDisabled) }.padding(EdgeInsets(top: 0, leading: 10, bottom: 0, trailing: 10)) - if viewModel.settingsShown { - SettingsView(viewModel: viewModel) + if viewModel.showConfigurationView { + ConfigurationView(viewModel: viewModel) + } else if viewModel.showSettingsInfo { + VStack { + Spacer() + Button("Open Settings") { + viewModel.showSettings = !viewModel.showSettings + } + Text("Open settings for basic virtual machine configuration, then press Start to install.") + .lineLimit(nil) + .font(.caption) + Spacer() + } } else { VirtualMachineView(virtualMachine: $viewModel.virtualMachine) } diff --git a/virtualOS/View/SettingsView.swift b/virtualOS/View/SettingsView.swift index 4087ca1..bce88ae 100644 --- a/virtualOS/View/SettingsView.swift +++ b/virtualOS/View/SettingsView.swift @@ -2,96 +2,102 @@ // SettingsView.swift // virtualOS // -// Created by Jahn Bertsch on 03.04.22. +// Created by Jahn Bertsch on 25.05.22. // #if arch(arm64) import SwiftUI +import UniformTypeIdentifiers struct SettingsView: View { @ObservedObject var viewModel: MainViewModel - fileprivate let sliderTextWidth = CGFloat(150) - @State fileprivate var cpuCountSliderValue: Float = 0 { - didSet { - viewModel.virtualMac.parameters.cpuCount = Int(cpuCountSliderValue) - } + + enum RestoreImageType: String { + case latest = "Downloads latest restore image from Apple." + case custom = "Select custom restore image (.ipsw)\nFor example, download from [https://ipsw.me](https://ipsw.me/product/Mac)" } - @State fileprivate var memorySliderValue: Float = 0 { - didSet { - viewModel.virtualMac.parameters.memorySizeInGB = UInt64(memorySliderValue) + @State var diskSize = String(UserDefaults.standard.diskSize) + @State var restoreImageType = RestoreImageType.latest + var restoreImageInfo: String { + if let restoreImageURL = viewModel.customRestoreImageURL { + return "Using \(restoreImageURL.path)" + } else { + return restoreImageType.rawValue } } - @State fileprivate var screenWidthValue: Float = 0 { - didSet { - viewModel.virtualMac.parameters.screenWidth = Int(screenWidthValue) + + fileprivate func selectRestoreImage() { + guard let ipswContentType = UTType(filenameExtension: "ipsw") else { + return } - } - @State fileprivate var screenHeightValue: Float = 0 { - didSet { - viewModel.virtualMac.parameters.screenHeight = Int(screenHeightValue) + let openPanel = NSOpenPanel() + openPanel.allowsMultipleSelection = false + openPanel.canChooseFiles = true + openPanel.allowedContentTypes = [ipswContentType] + if openPanel.runModal() == .OK, + let selectedURL = openPanel.url + { + viewModel.customRestoreImageURL = selectedURL } } - + var body: some View { VStack { - Spacer() - VStack { - let parameters = viewModel.virtualMac.parameters - Text("Virtual Machine Configuration").font(.title) - - Slider(value: Binding(get: { - cpuCountSliderValue - }, set: { (newValue) in - cpuCountSliderValue = newValue - }), in: Float(parameters.cpuCountMin) ... Float(parameters.cpuCountMax), step: 1) { - Text("CPU Count: \(viewModel.virtualMac.parameters.cpuCount)") - .frame(minWidth: sliderTextWidth, alignment: .leading) - } - - Slider(value: Binding(get: { - memorySliderValue - }, set: { (newValue) in - memorySliderValue = newValue - }), in: Float(parameters.memorySizeInGBMin) ... Float(parameters.memorySizeInGBMax), step: 1) { - Text("RAM: \(viewModel.virtualMac.parameters.memorySizeInGB) GB") - .frame(minWidth: sliderTextWidth, alignment: .leading) + Text("Settings") + Form { + HStack { + TextField("Hard Disk Size:", text: $diskSize) + .frame(maxWidth: 130) + .onChange(of: diskSize) { newValue in + if let newDiskSize = Int(diskSize) { + viewModel.diskSize = newDiskSize + } else { + diskSize = "" + } + } + Text("(in GB)") } - - Slider(value: Binding(get: { - screenWidthValue - }, set: { (newValue) in - screenWidthValue = newValue - }), in: 800 ... Float(NSScreen.main?.frame.width ?? CGFloat(parameters.screenWidth)), step: 100) { - Text("Screen Width: \(viewModel.virtualMac.parameters.screenWidth) px") - .frame(minWidth: sliderTextWidth, alignment: .leading) + + Picker("Restore Image:", selection: $restoreImageType) { + Text("Latest").tag(RestoreImageType.latest) + Text("Custom").tag(RestoreImageType.custom) } + .pickerStyle(.inline) - Slider(value: Binding(get: { - screenHeightValue - }, set: { (newValue) in - screenHeightValue = newValue - }), in: 600 ... Float(NSScreen.main?.frame.height ?? CGFloat(parameters.screenHeight)), step: 50) { - Text("Screen Height: \(viewModel.virtualMac.parameters.screenHeight) px") - .frame(minWidth: sliderTextWidth, alignment: .leading) + HStack { + Button("Select Restore Image") { + selectRestoreImage() + } + .disabled(restoreImageType == .latest) } + + Text(.init(restoreImageInfo)) + .font(.caption) + .frame(maxWidth: 270, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(nil) + .disabled(restoreImageType == .latest) } - .padding() - .overlay { - RoundedRectangle(cornerRadius: 10) - .stroke(.tertiary, lineWidth: 1) - } + .padding(.bottom) - Spacer() + Text("Virtual machine and restore image location:\n \(URL.basePath)\n\nTo open this directory:\nIn Finder, in the 'Go' menu, select 'Go to Folder' and enter the above URL.") + .frame(maxWidth: 370, alignment: .leading) + .fixedSize(horizontal: false, vertical: true) + .lineLimit(nil) + .padding(.bottom) + .textSelection(.enabled) + .font(.caption) + + Button("OK") { + viewModel.showSettings = !viewModel.showSettings + } + .keyboardShortcut(.defaultAction) } .padding() - .frame(maxWidth: 400) - .onAppear { - let parameters = viewModel.virtualMac.parameters - cpuCountSliderValue = Float(parameters.cpuCount) - memorySliderValue = Float(parameters.memorySizeInGB) - screenWidthValue = Float(parameters.screenWidth) - screenHeightValue = Float(parameters.screenHeight) + .frame(minWidth: 420) + .onAppear() { + diskSize = String(viewModel.diskSize) } } } @@ -100,7 +106,6 @@ struct SettingsViewProvider_Previews: PreviewProvider { static var previews: some View { VStack { SettingsView(viewModel: MainViewModel()) - .colorScheme(.light) } } } diff --git a/virtualOS/virtualOS.entitlements b/virtualOS/virtualOS.entitlements index 843b6ed..f1c6161 100644 --- a/virtualOS/virtualOS.entitlements +++ b/virtualOS/virtualOS.entitlements @@ -4,6 +4,8 @@ com.apple.security.app-sandbox + com.apple.security.files.user-selected.read-only + com.apple.security.network.client com.apple.security.virtualization diff --git a/virtualOS/virtualOSApp.swift b/virtualOS/virtualOSApp.swift index fe238dd..105ca79 100644 --- a/virtualOS/virtualOSApp.swift +++ b/virtualOS/virtualOSApp.swift @@ -7,18 +7,15 @@ import Foundation import SwiftUI - -func debugLog(_ message: String) { -#if DEBUG - print(message) -#endif -} +import OSLog typealias CompletionHander = (String?) -> Void typealias ProgressHandler = (Progress) -> Void @main struct virtualOSApp: App { + static let logger = Logger(subsystem: "com.github.yep.virtualOS", category: "main") + #if arch(arm64) @ObservedObject var viewModel = MainViewModel() #endif @@ -42,6 +39,9 @@ struct virtualOSApp: App { .alert(viewModel.licenseInformationTitleString, isPresented: $viewModel.showLicenseInformationModal, actions: {}, message: { Text(viewModel.licenseInformationString) }) + .sheet(isPresented: $viewModel.showSettings, content: { + SettingsView(viewModel: viewModel) + }) #else Text("Sorry, virtualization requires an Apple Silicon computer.") @@ -55,6 +55,11 @@ struct virtualOSApp: App { viewModel.showLicenseInformationModal = !viewModel.showLicenseInformationModal } } + CommandGroup(replacing: .appSettings) { + Button("Settings") { + viewModel.showSettings = !viewModel.showSettings + }.keyboardShortcut(",") + } CommandGroup(replacing: .newItem) {} CommandGroup(after: .newItem) { Button("Delete Restore Image", action: { @@ -72,4 +77,8 @@ struct virtualOSApp: App { } #endif } + + static func debugLog(_ message: String) { + Self.logger.notice("\(message, privacy: .public)") + } }