Skip to content

Commit

Permalink
Add option for setting shared folder and hard disk location
Browse files Browse the repository at this point in the history
Needs testing when distributed by Mac app store
  • Loading branch information
* committed May 5, 2023
1 parent 99c3ed6 commit a67f096
Show file tree
Hide file tree
Showing 11 changed files with 379 additions and 94 deletions.
12 changes: 8 additions & 4 deletions virtualOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C9927E238930048776B /* VirtualMacConfiguration.swift */; };
00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */; };
0114C02629AA2416004159AF /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0114C02529AA2416004159AF /* MenuCommands.swift */; };
01B042F229CD9F6A003CD5C2 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B042F129CD9F6A003CD5C2 /* Bookmark.swift */; };
01FCAD8429AB707C00F12689 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -69,6 +70,7 @@
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>"; };
0114C02529AA2416004159AF /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; };
01B042F129CD9F6A003CD5C2 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -175,6 +177,7 @@
0044A65427F601E60007988A /* MainViewModel.swift */,
007987B027E24A8400960D74 /* VirtualMac.swift */,
00989C9927E238930048776B /* VirtualMacConfiguration.swift */,
01B042F129CD9F6A003CD5C2 /* Bookmark.swift */,
);
path = Model;
sourceTree = "<group>";
Expand Down Expand Up @@ -333,6 +336,7 @@
006504E727F9D59300723BCA /* ConfigurationView.swift in Sources */,
0114C02629AA2416004159AF /* MenuCommands.swift in Sources */,
00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */,
01B042F229CD9F6A003CD5C2 /* Bookmark.swift in Sources */,
0090AF6127E25F6F0077D35F /* UInt64+Byte.swift in Sources */,
0044A65527F601E60007988A /* MainViewModel.swift in Sources */,
);
Expand Down Expand Up @@ -496,7 +500,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 9;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\"";
DEVELOPMENT_TEAM = 2AD47BTDQ6;
Expand All @@ -511,7 +515,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.2.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -529,7 +533,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 9;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\"";
DEVELOPMENT_TEAM = 2AD47BTDQ6;
Expand All @@ -544,7 +548,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = 1.2.2;
MARKETING_VERSION = 1.3;
PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down
38 changes: 31 additions & 7 deletions virtualOS/Extension/URL+Paths.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,37 @@ import Foundation
extension URL {
static let basePath = NSHomeDirectory() + "/Documents"
static let restoreImageURL = URL(fileURLWithPath: basePath + "/RestoreImage.ipsw")
static let bundleName = "virtualOS.bundle/"
static let defaultVmBundlePath = basePath + "/\(bundleName)"

static var vmBundleURL: URL {
return URL(fileURLWithPath: vmBundlePath)
}
static var diskImageURL: URL {
return URL(fileURLWithPath: vmBundlePath + "/Disk.img")
}
static var auxiliaryStorageURL: URL {
return URL(fileURLWithPath: vmBundlePath + "/AuxiliaryStorage")
}
static var machineIdentifierURL: URL {
return URL(fileURLWithPath: vmBundlePath + "/MachineIdentifier")
}
static var hardwareModelURL: URL {
return URL(fileURLWithPath: vmBundlePath + "/HardwareModel")
}
static var parametersURL: URL {
return URL(fileURLWithPath: vmBundlePath + "/Parameters.txt")
}

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")
static let machineIdentifierURL = URL(fileURLWithPath: vmBundlePath + "MachineIdentifier")
static let hardwareModelURL = URL(fileURLWithPath: vmBundlePath + "HardwareModel")
static let parametersURL = URL(fileURLWithPath: vmBundlePath + "Parameters.txt")
static var vmBundlePath: String {
if let hardDiskDirectoryBookmarkData = UserDefaults.standard.hardDiskDirectoryBookmarkData,
let hardDiskDirectoryURL = Bookmark.startAccess(data: hardDiskDirectoryBookmarkData, forType: .hardDisk)
{
let vmBundlePath = hardDiskDirectoryURL.appendingPathComponent(bundleName).path
return vmBundlePath
} else {
return URL.defaultVmBundlePath
}
}
}

15 changes: 13 additions & 2 deletions virtualOS/Extension/UserDefaults+Settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
import Foundation

extension UserDefaults {
fileprivate static let diskSizeKey = "diskSize"

fileprivate static let diskSizeKey = "diskSize"
fileprivate static let hardDiskBookmarkKey = "hardDiskBookmark"

var diskSize: Int {
get {
if object(forKey: Self.diskSizeKey) != nil {
Expand All @@ -22,4 +23,14 @@ extension UserDefaults {
synchronize()
}
}

var hardDiskDirectoryBookmarkData: Data? {
get {
return data(forKey: Self.hardDiskBookmarkKey)
}
set {
set(newValue, forKey: Self.hardDiskBookmarkKey)
synchronize()
}
}
}
4 changes: 4 additions & 0 deletions virtualOS/Model/ApplicationDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ class ApplicationDelegate: NSObject, NSApplicationDelegate {
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}

func applicationWillTerminate(_ notification: Notification) {
Bookmark.stopAllAccess()
}
}
66 changes: 66 additions & 0 deletions virtualOS/Model/Bookmark.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Data+Bookmark.swift
// virtualOS
//
// Created by Jahn Bertsch on 24.03.23.
//

import Foundation

struct Bookmark {
enum BookmarkType {
case hardDisk
case sharedFolder
}

fileprivate static var accessedURLs: [BookmarkType: URL] = [:]

static func createBookmarkData(fromUrl url: URL) -> Data? {
if let bookmarkData = try? url.bookmarkData(options: .withSecurityScope, relativeTo: nil) {
return bookmarkData
}
return nil
}

static func startAccess(data: Data?, forType key: BookmarkType) -> URL? {
var bookmarkDataIsStale = false
if let bookmarkData = data,
let bookmarkURL = try? URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &bookmarkDataIsStale),
!bookmarkDataIsStale
{
// stop accessing previous resource
if let previousURL = accessedURLs[key],
previousURL != bookmarkURL
{
previousURL.stopAccessingSecurityScopedResource()
}

if accessedURLs[key] == bookmarkURL {
// resource already accessed, do nothing
} else {
// start access resource
if !bookmarkURL.startAccessingSecurityScopedResource() {
// access failed
bookmarkURL.stopAccessingSecurityScopedResource()
return nil
}
accessedURLs[key] = bookmarkURL
}
return bookmarkURL
}

return nil
}

static func stopAccess(url: URL, forKey key: BookmarkType) {
url.stopAccessingSecurityScopedResource()
Self.accessedURLs[key] = nil
}

static func stopAllAccess() {
for (_, accessedURL) in accessedURLs {
accessedURL.stopAccessingSecurityScopedResource()
}
Self.accessedURLs = [:]
}
}
47 changes: 33 additions & 14 deletions virtualOS/Model/MainViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ final class MainViewModel: NSObject, ObservableObject {
@Published var virtualMac = VirtualMac()
@Published var virtualMachine: VZVirtualMachine?
@Published var customRestoreImageURL: URL?
@Published var customHardDiskURL: URL?
@Published var diskSize = UserDefaults.standard.diskSize {
didSet {
UserDefaults.standard.diskSize = diskSize
Expand All @@ -58,20 +59,31 @@ final class MainViewModel: NSObject, ObservableObject {
static var restoreImageExists: Bool {
return FileManager.default.fileExists(atPath: URL.restoreImageURL.path)
}

var sharedFolderExists: Bool {
if let hardDiskDirectoryBookmarkData = Bookmark.startAccess(data: virtualMac.parameters.sharedFolder, forType: .hardDisk) {
var isDirectory = ObjCBool(false)
if FileManager.default.fileExists(atPath: hardDiskDirectoryBookmarkData.path, isDirectory: &isDirectory),
isDirectory.boolValue == true
{
return true
}
}
return false
}
var showConfigurationView: Bool {
return (Self.diskImageExists || Self.restoreImageExists) && state == .Stopped
}
var showSettingsInfo: Bool {
return !Self.diskImageExists && state == .Stopped
}

// MARK: - Public

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

Expand Down Expand Up @@ -126,18 +138,33 @@ final class MainViewModel: NSObject, ObservableObject {
} else {
licenseInformationString = "License information not found"
}

licenseInformationTitleString = "virtualOS"
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
{
licenseInformationTitleString += " \(version) (Build \(build))"
}
}

func set(sharedFolderUrl: URL) {
if let sharedFolderData = Bookmark.createBookmarkData(fromUrl: sharedFolderUrl) {
_ = Bookmark.startAccess(data: sharedFolderData, forType: .sharedFolder)
virtualMac.parameters.sharedFolder = sharedFolderData
objectWillChange.send()
if let errorString = virtualMac.writeParametersToDisk() {
display(errorString: errorString)
}
}
}

// MARK: - Private

fileprivate func readParametersFromDisk() {
if let hardDiskDirectoryBookmarkData = UserDefaults.standard.hardDiskDirectoryBookmarkData {
_ = Bookmark.startAccess(data: hardDiskDirectoryBookmarkData, forType: .hardDisk)
}

if Self.diskImageExists {
// read previous vm settings
if let errorString = virtualMac.readFromDisk(delegate: self) {
Expand Down Expand Up @@ -176,7 +203,9 @@ final class MainViewModel: NSObject, ObservableObject {
self.display(errorString: "Download of restore image failed: \(errorString)")
} else {
virtualOSApp.debugLog("Download of restore image completed")
self.install(virtualMac: self.virtualMac)
DispatchQueue.main.async {
self.install(virtualMac: self.virtualMac)
}
}
}
}
Expand Down Expand Up @@ -306,16 +335,6 @@ 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)
}

fileprivate func handleCommandLineArguments() {
for arg in CommandLine.arguments where arg == "start" {
start()
Expand Down
4 changes: 3 additions & 1 deletion virtualOS/Model/VirtualMac.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ final class VirtualMac: ObservableObject {
screenHeight = try container.decode(Int.self, forKey: .screenHeight)
pixelsPerInch = try container.decode(Int.self, forKey: .pixelsPerInch)
microphoneEnabled = try container.decode(Bool.self, forKey: .microphoneEnabled)
sharedFolder = try container.decodeIfPresent(Data.self, forKey: .sharedFolder) ?? nil // optional
macAddress = try container.decodeIfPresent(String.self, forKey: .macAddress) ?? VZMACAddress.randomLocallyAdministered().string // optional
}

Expand All @@ -42,6 +43,7 @@ final class VirtualMac: ObservableObject {
var screenHeight = 900
var pixelsPerInch = 250
var microphoneEnabled = false
var sharedFolder: Data?
var macAddress = VZMACAddress.randomLocallyAdministered().string
}

Expand Down Expand Up @@ -166,7 +168,7 @@ final class VirtualMac: ObservableObject {
let virtualMachine = VZVirtualMachine(configuration: virtualMacConfiguration, queue: .main)
virtualMachine.delegate = delegate

virtualOSApp.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, screen size \(parameters.screenWidth)x\(parameters.screenHeight) px at \(parameters.pixelsPerInch) ppi, shared folder: \(Bookmark.startAccess(data: parameters.sharedFolder, forType: .sharedFolder)?.absoluteString ?? "none")")

return virtualMachine
}
Expand Down
17 changes: 17 additions & 0 deletions virtualOS/Model/VirtualMacConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration {
configureGraphicsDevice(parameters: parameters)
configureStorageDevice(parameters: parameters)
configureNetworkDevices(parameters: parameters)
if #available(macOS 13.0, *) {
configureSharedFolder(parameters: parameters)
}
}

// MARK: - Private
Expand Down Expand Up @@ -139,6 +142,20 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration {
virtualOSApp.debugLog("Error: could not create storage device")
}
}

@available(macOS 13.0, *)
fileprivate func configureSharedFolder(parameters: VirtualMac.Parameters) {
guard let hardDiskDirectoryBookmarkData = Bookmark.startAccess(data: parameters.sharedFolder, forType: .hardDisk) else {
return
}

let sharedDirectory = VZSharedDirectory(url: hardDiskDirectoryBookmarkData, readOnly: false)
let singleDirectoryShare = VZSingleDirectoryShare(directory: sharedDirectory)
let sharingConfiguration = VZVirtioFileSystemDeviceConfiguration(tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag)
sharingConfiguration.share = singleDirectoryShare

directorySharingDevices = [sharingConfiguration]
}

fileprivate func computeCPUCount() -> Int {
let totalAvailableCPUs = ProcessInfo.processInfo.processorCount
Expand Down
Loading

0 comments on commit a67f096

Please sign in to comment.