Skip to content

Commit a67f096

Browse files
author
*
committed
Add option for setting shared folder and hard disk location
Needs testing when distributed by Mac app store
1 parent 99c3ed6 commit a67f096

File tree

11 files changed

+379
-94
lines changed

11 files changed

+379
-94
lines changed

virtualOS.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00989C9927E238930048776B /* VirtualMacConfiguration.swift */; };
2626
00A4FFE8283E3D6F004DD9B3 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */; };
2727
0114C02629AA2416004159AF /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0114C02529AA2416004159AF /* MenuCommands.swift */; };
28+
01B042F229CD9F6A003CD5C2 /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B042F129CD9F6A003CD5C2 /* Bookmark.swift */; };
2829
01FCAD8429AB707C00F12689 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */; };
2930
/* End PBXBuildFile section */
3031

@@ -69,6 +70,7 @@
6970
00A4FFE7283E3D6F004DD9B3 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
7071
00BA26AC2826DAF200E80B76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
7172
0114C02529AA2416004159AF /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; };
73+
01B042F129CD9F6A003CD5C2 /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = "<group>"; };
7274
01FCAD8329AB707C00F12689 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = "<group>"; };
7375
/* End PBXFileReference section */
7476

@@ -175,6 +177,7 @@
175177
0044A65427F601E60007988A /* MainViewModel.swift */,
176178
007987B027E24A8400960D74 /* VirtualMac.swift */,
177179
00989C9927E238930048776B /* VirtualMacConfiguration.swift */,
180+
01B042F129CD9F6A003CD5C2 /* Bookmark.swift */,
178181
);
179182
path = Model;
180183
sourceTree = "<group>";
@@ -333,6 +336,7 @@
333336
006504E727F9D59300723BCA /* ConfigurationView.swift in Sources */,
334337
0114C02629AA2416004159AF /* MenuCommands.swift in Sources */,
335338
00989C9A27E238930048776B /* VirtualMacConfiguration.swift in Sources */,
339+
01B042F229CD9F6A003CD5C2 /* Bookmark.swift in Sources */,
336340
0090AF6127E25F6F0077D35F /* UInt64+Byte.swift in Sources */,
337341
0044A65527F601E60007988A /* MainViewModel.swift in Sources */,
338342
);
@@ -496,7 +500,7 @@
496500
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
497501
CODE_SIGN_STYLE = Automatic;
498502
COMBINE_HIDPI_IMAGES = YES;
499-
CURRENT_PROJECT_VERSION = 7;
503+
CURRENT_PROJECT_VERSION = 9;
500504
DEAD_CODE_STRIPPING = YES;
501505
DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\"";
502506
DEVELOPMENT_TEAM = 2AD47BTDQ6;
@@ -511,7 +515,7 @@
511515
"@executable_path/../Frameworks",
512516
);
513517
MACOSX_DEPLOYMENT_TARGET = 12.0;
514-
MARKETING_VERSION = 1.2.2;
518+
MARKETING_VERSION = 1.3;
515519
PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS;
516520
PRODUCT_NAME = "$(TARGET_NAME)";
517521
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -529,7 +533,7 @@
529533
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
530534
CODE_SIGN_STYLE = Automatic;
531535
COMBINE_HIDPI_IMAGES = YES;
532-
CURRENT_PROJECT_VERSION = 7;
536+
CURRENT_PROJECT_VERSION = 9;
533537
DEAD_CODE_STRIPPING = YES;
534538
DEVELOPMENT_ASSET_PATHS = "\"virtualOS/Preview Content\"";
535539
DEVELOPMENT_TEAM = 2AD47BTDQ6;
@@ -544,7 +548,7 @@
544548
"@executable_path/../Frameworks",
545549
);
546550
MACOSX_DEPLOYMENT_TARGET = 12.0;
547-
MARKETING_VERSION = 1.2.2;
551+
MARKETING_VERSION = 1.3;
548552
PRODUCT_BUNDLE_IDENTIFIER = com.github.yep.ios.virtualOS;
549553
PRODUCT_NAME = "$(TARGET_NAME)";
550554
SWIFT_EMIT_LOC_STRINGS = YES;

virtualOS/Extension/URL+Paths.swift

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,37 @@ import Foundation
88
extension URL {
99
static let basePath = NSHomeDirectory() + "/Documents"
1010
static let restoreImageURL = URL(fileURLWithPath: basePath + "/RestoreImage.ipsw")
11+
static let bundleName = "virtualOS.bundle/"
12+
static let defaultVmBundlePath = basePath + "/\(bundleName)"
13+
14+
static var vmBundleURL: URL {
15+
return URL(fileURLWithPath: vmBundlePath)
16+
}
17+
static var diskImageURL: URL {
18+
return URL(fileURLWithPath: vmBundlePath + "/Disk.img")
19+
}
20+
static var auxiliaryStorageURL: URL {
21+
return URL(fileURLWithPath: vmBundlePath + "/AuxiliaryStorage")
22+
}
23+
static var machineIdentifierURL: URL {
24+
return URL(fileURLWithPath: vmBundlePath + "/MachineIdentifier")
25+
}
26+
static var hardwareModelURL: URL {
27+
return URL(fileURLWithPath: vmBundlePath + "/HardwareModel")
28+
}
29+
static var parametersURL: URL {
30+
return URL(fileURLWithPath: vmBundlePath + "/Parameters.txt")
31+
}
1132

12-
static let vmBundlePath = basePath + "/virtualOS.bundle/"
13-
static let vmBundleURL = URL(fileURLWithPath: vmBundlePath)
14-
static let diskImageURL = URL(fileURLWithPath: vmBundlePath + "Disk.img")
15-
static let auxiliaryStorageURL = URL(fileURLWithPath: vmBundlePath + "AuxiliaryStorage")
16-
static let machineIdentifierURL = URL(fileURLWithPath: vmBundlePath + "MachineIdentifier")
17-
static let hardwareModelURL = URL(fileURLWithPath: vmBundlePath + "HardwareModel")
18-
static let parametersURL = URL(fileURLWithPath: vmBundlePath + "Parameters.txt")
33+
static var vmBundlePath: String {
34+
if let hardDiskDirectoryBookmarkData = UserDefaults.standard.hardDiskDirectoryBookmarkData,
35+
let hardDiskDirectoryURL = Bookmark.startAccess(data: hardDiskDirectoryBookmarkData, forType: .hardDisk)
36+
{
37+
let vmBundlePath = hardDiskDirectoryURL.appendingPathComponent(bundleName).path
38+
return vmBundlePath
39+
} else {
40+
return URL.defaultVmBundlePath
41+
}
42+
}
1943
}
2044

virtualOS/Extension/UserDefaults+Settings.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
import Foundation
99

1010
extension UserDefaults {
11-
fileprivate static let diskSizeKey = "diskSize"
12-
11+
fileprivate static let diskSizeKey = "diskSize"
12+
fileprivate static let hardDiskBookmarkKey = "hardDiskBookmark"
13+
1314
var diskSize: Int {
1415
get {
1516
if object(forKey: Self.diskSizeKey) != nil {
@@ -22,4 +23,14 @@ extension UserDefaults {
2223
synchronize()
2324
}
2425
}
26+
27+
var hardDiskDirectoryBookmarkData: Data? {
28+
get {
29+
return data(forKey: Self.hardDiskBookmarkKey)
30+
}
31+
set {
32+
set(newValue, forKey: Self.hardDiskBookmarkKey)
33+
synchronize()
34+
}
35+
}
2536
}

virtualOS/Model/ApplicationDelegate.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ class ApplicationDelegate: NSObject, NSApplicationDelegate {
1111
func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
1212
return true
1313
}
14+
15+
func applicationWillTerminate(_ notification: Notification) {
16+
Bookmark.stopAllAccess()
17+
}
1418
}

virtualOS/Model/Bookmark.swift

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Data+Bookmark.swift
3+
// virtualOS
4+
//
5+
// Created by Jahn Bertsch on 24.03.23.
6+
//
7+
8+
import Foundation
9+
10+
struct Bookmark {
11+
enum BookmarkType {
12+
case hardDisk
13+
case sharedFolder
14+
}
15+
16+
fileprivate static var accessedURLs: [BookmarkType: URL] = [:]
17+
18+
static func createBookmarkData(fromUrl url: URL) -> Data? {
19+
if let bookmarkData = try? url.bookmarkData(options: .withSecurityScope, relativeTo: nil) {
20+
return bookmarkData
21+
}
22+
return nil
23+
}
24+
25+
static func startAccess(data: Data?, forType key: BookmarkType) -> URL? {
26+
var bookmarkDataIsStale = false
27+
if let bookmarkData = data,
28+
let bookmarkURL = try? URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &bookmarkDataIsStale),
29+
!bookmarkDataIsStale
30+
{
31+
// stop accessing previous resource
32+
if let previousURL = accessedURLs[key],
33+
previousURL != bookmarkURL
34+
{
35+
previousURL.stopAccessingSecurityScopedResource()
36+
}
37+
38+
if accessedURLs[key] == bookmarkURL {
39+
// resource already accessed, do nothing
40+
} else {
41+
// start access resource
42+
if !bookmarkURL.startAccessingSecurityScopedResource() {
43+
// access failed
44+
bookmarkURL.stopAccessingSecurityScopedResource()
45+
return nil
46+
}
47+
accessedURLs[key] = bookmarkURL
48+
}
49+
return bookmarkURL
50+
}
51+
52+
return nil
53+
}
54+
55+
static func stopAccess(url: URL, forKey key: BookmarkType) {
56+
url.stopAccessingSecurityScopedResource()
57+
Self.accessedURLs[key] = nil
58+
}
59+
60+
static func stopAllAccess() {
61+
for (_, accessedURL) in accessedURLs {
62+
accessedURL.stopAccessingSecurityScopedResource()
63+
}
64+
Self.accessedURLs = [:]
65+
}
66+
}

virtualOS/Model/MainViewModel.swift

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ final class MainViewModel: NSObject, ObservableObject {
3838
@Published var virtualMac = VirtualMac()
3939
@Published var virtualMachine: VZVirtualMachine?
4040
@Published var customRestoreImageURL: URL?
41+
@Published var customHardDiskURL: URL?
4142
@Published var diskSize = UserDefaults.standard.diskSize {
4243
didSet {
4344
UserDefaults.standard.diskSize = diskSize
@@ -58,20 +59,31 @@ final class MainViewModel: NSObject, ObservableObject {
5859
static var restoreImageExists: Bool {
5960
return FileManager.default.fileExists(atPath: URL.restoreImageURL.path)
6061
}
61-
62+
var sharedFolderExists: Bool {
63+
if let hardDiskDirectoryBookmarkData = Bookmark.startAccess(data: virtualMac.parameters.sharedFolder, forType: .hardDisk) {
64+
var isDirectory = ObjCBool(false)
65+
if FileManager.default.fileExists(atPath: hardDiskDirectoryBookmarkData.path, isDirectory: &isDirectory),
66+
isDirectory.boolValue == true
67+
{
68+
return true
69+
}
70+
}
71+
return false
72+
}
6273
var showConfigurationView: Bool {
6374
return (Self.diskImageExists || Self.restoreImageExists) && state == .Stopped
6475
}
6576
var showSettingsInfo: Bool {
6677
return !Self.diskImageExists && state == .Stopped
6778
}
79+
80+
// MARK: - Public
6881

6982
override init() {
7083
super.init()
7184
updateLabels(for: state)
7285
readParametersFromDisk()
7386
loadLicenseInformationFromBundle()
74-
moveFilesAfterUpdate()
7587
handleCommandLineArguments()
7688
}
7789

@@ -126,18 +138,33 @@ final class MainViewModel: NSObject, ObservableObject {
126138
} else {
127139
licenseInformationString = "License information not found"
128140
}
129-
141+
130142
licenseInformationTitleString = "virtualOS"
131143
if let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
132144
let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
133145
{
134146
licenseInformationTitleString += " \(version) (Build \(build))"
135147
}
136148
}
149+
150+
func set(sharedFolderUrl: URL) {
151+
if let sharedFolderData = Bookmark.createBookmarkData(fromUrl: sharedFolderUrl) {
152+
_ = Bookmark.startAccess(data: sharedFolderData, forType: .sharedFolder)
153+
virtualMac.parameters.sharedFolder = sharedFolderData
154+
objectWillChange.send()
155+
if let errorString = virtualMac.writeParametersToDisk() {
156+
display(errorString: errorString)
157+
}
158+
}
159+
}
137160

138161
// MARK: - Private
139162

140163
fileprivate func readParametersFromDisk() {
164+
if let hardDiskDirectoryBookmarkData = UserDefaults.standard.hardDiskDirectoryBookmarkData {
165+
_ = Bookmark.startAccess(data: hardDiskDirectoryBookmarkData, forType: .hardDisk)
166+
}
167+
141168
if Self.diskImageExists {
142169
// read previous vm settings
143170
if let errorString = virtualMac.readFromDisk(delegate: self) {
@@ -176,7 +203,9 @@ final class MainViewModel: NSObject, ObservableObject {
176203
self.display(errorString: "Download of restore image failed: \(errorString)")
177204
} else {
178205
virtualOSApp.debugLog("Download of restore image completed")
179-
self.install(virtualMac: self.virtualMac)
206+
DispatchQueue.main.async {
207+
self.install(virtualMac: self.virtualMac)
208+
}
180209
}
181210
}
182211
}
@@ -306,16 +335,6 @@ final class MainViewModel: NSObject, ObservableObject {
306335
statusLabel = statusText
307336
}
308337

309-
fileprivate func moveFilesAfterUpdate() {
310-
let oldRestoreImageLocation = URL(fileURLWithPath: NSHomeDirectory() + "/RestoreImage.ipsw")
311-
let newRestoreImageLocation = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/RestoreImage.ipsw")
312-
try? FileManager.default.moveItem(at: oldRestoreImageLocation, to: newRestoreImageLocation)
313-
314-
let oldVirtualMachineLocation = URL(fileURLWithPath: NSHomeDirectory() + "/virtualOS.bundle")
315-
let newVirtualMachineLocation = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/virtualOS.bundle")
316-
try? FileManager.default.moveItem(at: oldVirtualMachineLocation, to: newVirtualMachineLocation)
317-
}
318-
319338
fileprivate func handleCommandLineArguments() {
320339
for arg in CommandLine.arguments where arg == "start" {
321340
start()

virtualOS/Model/VirtualMac.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ final class VirtualMac: ObservableObject {
2727
screenHeight = try container.decode(Int.self, forKey: .screenHeight)
2828
pixelsPerInch = try container.decode(Int.self, forKey: .pixelsPerInch)
2929
microphoneEnabled = try container.decode(Bool.self, forKey: .microphoneEnabled)
30+
sharedFolder = try container.decodeIfPresent(Data.self, forKey: .sharedFolder) ?? nil // optional
3031
macAddress = try container.decodeIfPresent(String.self, forKey: .macAddress) ?? VZMACAddress.randomLocallyAdministered().string // optional
3132
}
3233

@@ -42,6 +43,7 @@ final class VirtualMac: ObservableObject {
4243
var screenHeight = 900
4344
var pixelsPerInch = 250
4445
var microphoneEnabled = false
46+
var sharedFolder: Data?
4547
var macAddress = VZMACAddress.randomLocallyAdministered().string
4648
}
4749

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

169-
virtualOSApp.debugLog("Using \(virtualMacConfiguration.cpuCount) cores, \(virtualMacConfiguration.memorySize.bytesToGigabytes()) GB RAM and screen size \(parameters.screenWidth)x\(parameters.screenHeight) px at \(parameters.pixelsPerInch) ppi")
171+
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")")
170172

171173
return virtualMachine
172174
}

virtualOS/Model/VirtualMacConfiguration.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration {
5858
configureGraphicsDevice(parameters: parameters)
5959
configureStorageDevice(parameters: parameters)
6060
configureNetworkDevices(parameters: parameters)
61+
if #available(macOS 13.0, *) {
62+
configureSharedFolder(parameters: parameters)
63+
}
6164
}
6265

6366
// MARK: - Private
@@ -139,6 +142,20 @@ final class VirtualMacConfiguration: VZVirtualMachineConfiguration {
139142
virtualOSApp.debugLog("Error: could not create storage device")
140143
}
141144
}
145+
146+
@available(macOS 13.0, *)
147+
fileprivate func configureSharedFolder(parameters: VirtualMac.Parameters) {
148+
guard let hardDiskDirectoryBookmarkData = Bookmark.startAccess(data: parameters.sharedFolder, forType: .hardDisk) else {
149+
return
150+
}
151+
152+
let sharedDirectory = VZSharedDirectory(url: hardDiskDirectoryBookmarkData, readOnly: false)
153+
let singleDirectoryShare = VZSingleDirectoryShare(directory: sharedDirectory)
154+
let sharingConfiguration = VZVirtioFileSystemDeviceConfiguration(tag: VZVirtioFileSystemDeviceConfiguration.macOSGuestAutomountTag)
155+
sharingConfiguration.share = singleDirectoryShare
156+
157+
directorySharingDevices = [sharingConfiguration]
158+
}
142159

143160
fileprivate func computeCPUCount() -> Int {
144161
let totalAvailableCPUs = ProcessInfo.processInfo.processorCount

0 commit comments

Comments
 (0)