Skip to content

Commit

Permalink
Add settings view and support for custom ipsw restore images
Browse files Browse the repository at this point in the history
  • Loading branch information
* committed Jul 6, 2022
1 parent 6c260d5 commit 820263f
Show file tree
Hide file tree
Showing 18 changed files with 241 additions and 136 deletions.
42 changes: 31 additions & 11 deletions virtualOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 */
Expand All @@ -43,9 +45,10 @@

/* Begin PBXFileReference section */
0005A77927E2809E0013BE83 /* VirtualMachineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMachineView.swift; sourceTree = "<group>"; };
002E64912871B2DD00CE95A0 /* UserDefaults+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Settings.swift"; sourceTree = "<group>"; };
0044A65427F601E60007988A /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = "<group>"; };
0044A65927F76BD30007988A /* URL+Paths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URL+Paths.swift"; sourceTree = "<group>"; };
006504E627F9D59300723BCA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
006504E627F9D59300723BCA /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = "<group>"; };
007987AE27E2487200960D74 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
007987B027E24A8400960D74 /* VirtualMac.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMac.swift; sourceTree = "<group>"; };
0090AF6027E25F6F0077D35F /* UInt64+Byte.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UInt64+Byte.swift"; sourceTree = "<group>"; };
Expand All @@ -61,6 +64,7 @@
00989C8127E2340D0048776B /* virtualOSUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = virtualOSUITestsLaunchTests.swift; sourceTree = "<group>"; };
00989C9427E236A10048776B /* MainView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
00989C9927E238930048776B /* VirtualMacConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VirtualMacConfiguration.swift; sourceTree = "<group>"; };
00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
00BA26AC2826DAF200E80B76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -94,6 +98,7 @@
children = (
0090AF6027E25F6F0077D35F /* UInt64+Byte.swift */,
0044A65927F76BD30007988A /* URL+Paths.swift */,
002E64912871B2DD00CE95A0 /* UserDefaults+Settings.swift */,
);
path = Extension;
sourceTree = "<group>";
Expand Down Expand Up @@ -174,7 +179,8 @@
children = (
00989C9427E236A10048776B /* MainView.swift */,
0005A77927E2809E0013BE83 /* VirtualMachineView.swift */,
006504E627F9D59300723BCA /* SettingsView.swift */,
006504E627F9D59300723BCA /* ConfigurationView.swift */,
00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */,
);
path = View;
sourceTree = "<group>";
Expand Down Expand Up @@ -243,7 +249,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1330;
LastUpgradeCheck = 1330;
LastUpgradeCheck = 1400;
TargetAttributes = {
00989C5F27E2340C0048776B = {
CreatedOnToolsVersion = 13.3;
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1330"
LastUpgradeVersion = "1400"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified virtualOS/Assets.xcassets/AppIcon.appiconset/virtualOS-macOS-64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions virtualOS/Extension/URL+Paths.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import Foundation

extension URL {
static let restoreImageURL = URL(fileURLWithPath: NSHomeDirectory() + "/RestoreImage.ipsw")
static let basePath = NSHomeDirectory() + "/Documents"
static let restoreImageURL = URL(fileURLWithPath: basePath + "/RestoreImage.ipsw")

static let vmBundlePath = NSHomeDirectory() + "/virtualOS.bundle/"
static let vmBundlePath = basePath + "/virtualOS.bundle/"
static let vmBundleURL = URL(fileURLWithPath: vmBundlePath)
static let diskImageURL = URL(fileURLWithPath: vmBundlePath + "Disk.img")
static let auxiliaryStorageURL = URL(fileURLWithPath: vmBundlePath + "AuxiliaryStorage")
Expand Down
25 changes: 25 additions & 0 deletions virtualOS/Extension/UserDefaults+Settings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// UserDefaults+Settings.swift
// virtualOS
//
// Created by Jahn Bertsch on 03.07.22.
//

import Foundation

extension UserDefaults {
fileprivate static let diskSizeKey = "diskSize"

var diskSize: Int {
get {
if object(forKey: Self.diskSizeKey) != nil {
return integer(forKey: Self.diskSizeKey)
}
return 60 // default value
}
set {
set(newValue, forKey: Self.diskSizeKey)
synchronize()
}
}
}
52 changes: 37 additions & 15 deletions virtualOS/Model/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import Foundation
import Virtualization
import OSLog

final class MainViewModel: NSObject, ObservableObject {
enum State: String {
Expand All @@ -20,21 +21,28 @@ final class MainViewModel: NSObject, ObservableObject {
case Stopped
}

@Published var virtualMac = VirtualMac()
@Published var virtualMachine: VZVirtualMachine?
@Published var statusLabel = ""
@Published var buttonLabel = ""
@Published var buttonDisabled = false
@Published var progress: Progress?
@Published var showLicenseInformationModal = false
@Published var showConfirmationAlert = false
@Published var showSettings = false
@Published var licenseInformationTitleString = ""
@Published var licenseInformationString = ""
@Published var confirmationText = ""
@Published var confirmationHandler: CompletionHander = {_ in}
@Published var virtualMac = VirtualMac()
@Published var virtualMachine: VZVirtualMachine?
@Published var customRestoreImageURL: URL?
@Published var diskSize = UserDefaults.standard.diskSize {
didSet {
UserDefaults.standard.diskSize = diskSize
}
}
@Published var state = State.Stopped {
didSet {
debugLog(self.state.rawValue)
virtualOSApp.debugLog(self.state.rawValue)
updateLabels(for: self.state)
}
}
Expand All @@ -47,15 +55,20 @@ final class MainViewModel: NSObject, ObservableObject {
static var restoreImageExists: Bool {
return FileManager.default.fileExists(atPath: URL.restoreImageURL.path)
}
var settingsShown: Bool {

var showConfigurationView: Bool {
return (Self.diskImageExists || Self.restoreImageExists) && state == .Stopped
}
var showSettingsInfo: Bool {
return !Self.diskImageExists && state == .Stopped
}

override init() {
super.init()
updateLabels(for: state)
readParametersFromDisk()
loadLicenseInformationFromBundle()
moveFilesAfterUpdate()
}

func buttonPressed() {
Expand Down Expand Up @@ -101,9 +114,8 @@ final class MainViewModel: NSObject, ObservableObject {
func loadLicenseInformationFromBundle() {
if let filepath = Bundle.main.path(forResource: "LICENSE", ofType: "") {
do {
licenseInformationString = "Restore image and virtual machine are stored at:\n\(NSHomeDirectory())\n\n\n\n"
let contents = try String(contentsOfFile: filepath)
licenseInformationString += contents
licenseInformationString = contents
} catch {
licenseInformationString = "Failed to load license information"
}
Expand All @@ -128,7 +140,7 @@ final class MainViewModel: NSObject, ObservableObject {
display(errorString: errorString)
}
} else if Self.restoreImageExists {
virtualMac.loadParametersFromRestoreImage { (errorString: String?) in
virtualMac.loadParametersFromRestoreImage(customRestoreImageURL: nil) { (errorString: String?) in
if let errorString = errorString {
self.display(errorString: errorString)
}
Expand All @@ -137,10 +149,10 @@ final class MainViewModel: NSObject, ObservableObject {
}

fileprivate func start() {
debugLog("Using storage directory \(URL.vmBundlePath)")
virtualOSApp.debugLog("Using storage directory \(URL.vmBundlePath)")
if FileManager.default.fileExists(atPath: URL.diskImageURL.path) {
startFromDiskImage()
} else if FileManager.default.fileExists(atPath: URL.restoreImageURL.path) {
} else if FileManager.default.fileExists(atPath: URL.restoreImageURL.path) || customRestoreImageURL != nil {
install(virtualMac: virtualMac)
} else {
downloadAndInstall()
Expand All @@ -152,23 +164,23 @@ final class MainViewModel: NSObject, ObservableObject {
buttonLabel = "Stop"

virtualMac.downloadRestoreImage { (progress: Progress) in
debugLog("Download progress: \(progress.fractionCompleted * 100)%")
virtualOSApp.debugLog("Download progress: \(progress.fractionCompleted * 100)%")
self.progress = progress
self.updateLabels(for: self.state)
} completionHandler: { (errorString: String?) in
if let errorString = errorString {
self.display(errorString: "Download of restore image failed: \(errorString)")
} else {
debugLog("Download of restore image completed")
virtualOSApp.debugLog("Download of restore image completed")
self.install(virtualMac: self.virtualMac)
}
}
}

fileprivate func install(virtualMac: VirtualMac) {
state = .Installing
virtualMac.install(delegate: self) { (progress: Progress) in
debugLog("Install progress: \(progress.completedUnitCount)%")
virtualMac.install(delegate: self, customRestoreImageURL: customRestoreImageURL) { (progress: Progress) in
virtualOSApp.debugLog("Install progress: \(progress.completedUnitCount)%")
self.progress = progress
self.updateLabels(for: self.state)
} completionHandler: { (errorString: String?, virtualMachine: VZVirtualMachine?) in
Expand Down Expand Up @@ -228,7 +240,7 @@ final class MainViewModel: NSObject, ObservableObject {
}

fileprivate func display(errorString: String) {
debugLog(errorString)
virtualOSApp.debugLog(errorString)

let displayErrorString = {
self.state = .Stopped
Expand Down Expand Up @@ -258,7 +270,7 @@ final class MainViewModel: NSObject, ObservableObject {
if let progress = progress {
statusLabel = "Installing macOS \(virtualMac.versionString): "
if progress.completedUnitCount == 0 {
statusLabel = statusLabel + "Waiting for begin …"
statusLabel = statusLabel + "Waiting for begin, this may take some time"
} else {
statusLabel = statusLabel + "\(progress.completedUnitCount)%"
}
Expand Down Expand Up @@ -289,6 +301,16 @@ final class MainViewModel: NSObject, ObservableObject {

statusLabel = statusText
}

fileprivate func moveFilesAfterUpdate() {
let oldRestoreImageLocation = URL(fileURLWithPath: NSHomeDirectory() + "/RestoreImage.ipsw")
let newRestoreImageLocation = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/RestoreImage.ipsw")
try? FileManager.default.moveItem(at: oldRestoreImageLocation, to: newRestoreImageLocation)

let oldVirtualMachineLocation = URL(fileURLWithPath: NSHomeDirectory() + "/virtualOS.bundle")
let newVirtualMachineLocation = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/virtualOS.bundle")
try? FileManager.default.moveItem(at: oldVirtualMachineLocation, to: newVirtualMachineLocation)
}
}

extension MainViewModel: VZVirtualMachineDelegate {
Expand Down
Loading

0 comments on commit 820263f

Please sign in to comment.