Explorar el Código

Merge branch 'master' into dev

osy hace 4 años
padre
commit
a192459665

+ 4 - 2
Configuration/UTMConfiguration+Drives.h

@@ -38,8 +38,10 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)migrateDriveConfigurationIfNecessary;
 - (void)recoverOrphanedDrives;
 
-- (NSInteger)newDrive:(NSString *)name type:(UTMDiskImageType)type interface:(NSString *)interface;
-- (NSInteger)newRemovableDrive:(UTMDiskImageType)type interface:(NSString *)interface;
+- (NSInteger)newDrive:(NSString *)name path:(NSString *)path type:(UTMDiskImageType)type interface:(NSString *)interface;
+- (NSInteger)newRemovableDrive:(NSString *)name type:(UTMDiskImageType)type interface:(NSString *)interface;
+- (nullable NSString *)driveNameForIndex:(NSInteger)index;
+- (void)setDriveName:(NSString *)name forIndex:(NSInteger)index;
 - (nullable NSString *)driveImagePathForIndex:(NSInteger)index;
 - (void)setImagePath:(NSString *)path forIndex:(NSInteger)index;
 - (nullable NSString *)driveInterfaceTypeForIndex:(NSInteger)index;

+ 34 - 4
Configuration/UTMConfiguration+Drives.m

@@ -20,6 +20,7 @@
 
 extern const NSString *const kUTMConfigDrivesKey;
 
+static const NSString *const kUTMConfigDriveNameKey = @"DriveName";
 static const NSString *const kUTMConfigImagePathKey = @"ImagePath";
 static const NSString *const kUTMConfigImageTypeKey = @"ImageType";
 static const NSString *const kUTMConfigInterfaceTypeKey = @"InterfaceType";
@@ -58,6 +59,19 @@ static const NSString *const kUTMConfigCdromKey = @"Cdrom";
             [obj removeObjectForKey:kUTMConfigCdromKey];
         }
     }];
+    // add drive name
+    BOOL hasEmpty = NO;
+    for (NSInteger i = 0; i < self.countDrives; i++) {
+        if ([self driveNameForIndex:i] == nil) {
+            hasEmpty = YES;
+            break;
+        }
+    }
+    if (hasEmpty) { // reset all names if any are empty
+        for (NSInteger i = 0; i < self.countDrives; i++) {
+            [self setDriveName:[NSString stringWithFormat:@"drive%ld", i] forIndex:i];
+        }
+    }
 }
 
 #pragma mark - Orphan drives
@@ -90,7 +104,8 @@ static const NSString *const kUTMConfigCdromKey = @"Cdrom";
 - (void)recoverOrphanedDrives {
     NSArray<NSString *> *orphans = self.orphanedDrives;
     for (NSInteger i = 0; i < orphans.count; i++) {
-        [self newDrive:orphans[i] type:UTMDiskImageTypeNone interface:@""];
+        NSString *name = [NSUUID UUID].UUIDString;
+        [self newDrive:name path:orphans[i] type:UTMDiskImageTypeNone interface:@""];
     }
 }
 
@@ -100,11 +115,12 @@ static const NSString *const kUTMConfigCdromKey = @"Cdrom";
     return [self.rootDict[kUTMConfigDrivesKey] count];
 }
 
-- (NSInteger)newDrive:(NSString *)name type:(UTMDiskImageType)type interface:(NSString *)interface {
+- (NSInteger)newDrive:(NSString *)name path:(NSString *)path type:(UTMDiskImageType)type interface:(NSString *)interface {
     NSInteger index = [self countDrives];
     NSString *strType = [UTMConfiguration supportedImageTypes][type];
     NSMutableDictionary *drive = [[NSMutableDictionary alloc] initWithDictionary:@{
-        kUTMConfigImagePathKey: name,
+        kUTMConfigDriveNameKey: name,
+        kUTMConfigImagePathKey: path,
         kUTMConfigImageTypeKey: strType,
         kUTMConfigInterfaceTypeKey: interface
     }];
@@ -113,10 +129,11 @@ static const NSString *const kUTMConfigCdromKey = @"Cdrom";
     return index;
 }
 
-- (NSInteger)newRemovableDrive:(UTMDiskImageType)type interface:(NSString *)interface {
+- (NSInteger)newRemovableDrive:(NSString *)name type:(UTMDiskImageType)type interface:(NSString *)interface {
     NSInteger index = [self countDrives];
     NSString *strType = [UTMConfiguration supportedImageTypes][type];
     NSMutableDictionary *drive = [[NSMutableDictionary alloc] initWithDictionary:@{
+        kUTMConfigDriveNameKey: name,
         kUTMConfigRemovableKey: @(YES),
         kUTMConfigImageTypeKey: strType,
         kUTMConfigInterfaceTypeKey: interface
@@ -126,6 +143,19 @@ static const NSString *const kUTMConfigCdromKey = @"Cdrom";
     return index;
 }
 
+- (nullable NSString *)driveNameForIndex:(NSInteger)index {
+    if (index >= self.countDrives) {
+        return nil;
+    } else {
+        return self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigDriveNameKey];
+    }
+}
+
+- (void)setDriveName:(NSString *)name forIndex:(NSInteger)index {
+    [self propertyWillChange];
+    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigDriveNameKey] = name;
+}
+
 - (nullable NSString *)driveImagePathForIndex:(NSInteger)index {
     if (index >= self.countDrives) {
         return nil;

+ 9 - 2
Managers/UTMQemuSystem.m

@@ -192,6 +192,11 @@ static size_t sysctl_read(const char *name) {
     } else if ([interface isEqualToString:@"usb"]) {
         [self pushArgv:@"-device"];
         [self pushArgv:[NSString stringWithFormat:@"usb-storage,drive=%@,removable=%@,bootindex=%lu", identifier, removable ? @"true" : @"false", bootindex++]];
+    } else if ([interface isEqualToString:@"floppy"] && [self.configuration.systemTarget hasPrefix:@"q35"]) {
+        [self pushArgv:@"-device"];
+        [self pushArgv:[NSString stringWithFormat:@"isa-fdc,id=fdc%lu,bootindexA=%lu", busindex, bootindex++]];
+        [self pushArgv:@"-device"];
+        [self pushArgv:[NSString stringWithFormat:@"floppy,unit=0,bus=fdc%lu.0,drive=%@", busindex++, identifier]];
     } else {
         return interface; // no expand needed
     }
@@ -247,7 +252,7 @@ static size_t sysctl_read(const char *name) {
             case UTMDiskImageTypeCD: {
                 NSString *interface = [self.configuration driveInterfaceTypeForIndex:i];
                 BOOL removable = (type == UTMDiskImageTypeCD) || [self.configuration driveRemovableForIndex:i];
-                NSString *identifier = [NSString stringWithFormat:@"drive%lu", i];
+                NSString *identifier = [self.configuration driveNameForIndex:i];
                 NSString *realInterface = [self expandDriveInterface:interface identifier:identifier removable:removable busInterfaceMap:busInterfaceMap];
                 NSString *drive;
                 [self pushArgv:@"-drive"];
@@ -545,13 +550,15 @@ static size_t sysctl_read(const char *name) {
     [self architectureSpecificConfiguration];
     [self targetSpecificConfiguration];
     // legacy boot order; new bootindex uses drive ordering
+    [self pushArgv:@"-boot"];
     if (self.configuration.systemBootDevice.length > 0 && ![self.configuration.systemBootDevice isEqualToString:@"hdd"]) {
-        [self pushArgv:@"-boot"];
         if ([self.configuration.systemBootDevice isEqualToString:@"floppy"]) {
             [self pushArgv:@"order=ab"];
         } else {
             [self pushArgv:@"order=d"];
         }
+    } else {
+        [self pushArgv:@"menu=on"];
     }
     [self pushArgv:@"-m"];
     [self pushArgv:[self.configuration.systemMemory stringValue]];

+ 1 - 1
Managers/UTMVirtualMachine+Drives.m

@@ -42,7 +42,7 @@ extern NSString *const kUTMErrorDomain;
         drive.index = i;
         drive.imageType = [self.configuration driveImageTypeForIndex:i];
         drive.interface = [self.configuration driveInterfaceTypeForIndex:i];
-        drive.name = [NSString stringWithFormat:@"drive%lu", i];
+        drive.name = [self.configuration driveNameForIndex:i];
         if ([self.configuration driveRemovableForIndex:i]) {
             // removable drive -> path stored only in viewState
             NSString *path = [self.viewState pathForRemovableDrive:drive.name];

+ 3 - 3
Platform/Shared/VMDetailsView.swift

@@ -199,9 +199,9 @@ struct VMDetailsView_Previews: PreviewProvider {
         VMDetailsView(vm: UTMVirtualMachine(configuration: config, withDestinationURL: URL(fileURLWithPath: "")))
         .onAppear {
             config.shareDirectoryEnabled = true
-            config.newDrive("", type: .disk, interface: "ide")
-            config.newDrive("", type: .disk, interface: "sata")
-            config.newDrive("", type: .CD, interface: "ide")
+            config.newDrive("", path: "", type: .disk, interface: "ide")
+            config.newDrive("", path: "", type: .disk, interface: "sata")
+            config.newDrive("", path: "", type: .CD, interface: "ide")
         }
     }
 }

+ 9 - 13
Platform/Shared/VMPlaceholderView.swift

@@ -23,30 +23,26 @@ struct VMPlaceholderView: View {
     
     var body: some View {
         VStack {
-            Spacer()
             HStack {
-                Spacer()
                 Text("Welcome to UTM").font(.title)
-                Spacer()
             }
             HStack {
-                Spacer()
                 TileButton(titleKey: "Create a New Virtual Machine", systemImage: "plus.circle") {
                     data.newVM()
                 }
-                TileButton(titleKey: "Browse UTM Gallery", systemImage: "square.grid.3x2") {
-                    openURL(URL(string: "https://getutm.app/gallery/")!)
+                TileButton(titleKey: "Browse UTM Gallery", systemImage: "arrow.down.circle") {
+                    openURL(URL(string: "https://mac.getutm.app/gallery/")!)
                 }
-                TileButton(titleKey: "User Guide", systemImage: "questionmark.circle") {
-                    #if os(macOS)
+            }
+            HStack {
+                /// Link to Mac sites on all platforms because they are more up to date
+                TileButton(titleKey: "User Guide", systemImage: "book.circle") {
                     openURL(URL(string: "https://mac.getutm.app/guide/")!)
-                    #else
-                    openURL(URL(string: "https://getutm.app/guide_v2/")!)
-                    #endif
                 }
-                Spacer()
+                TileButton(titleKey: "Support", systemImage: "questionmark.circle") {
+                    openURL(URL(string: "https://mac.getutm.app/support/")!)
+                }
             }
-            Spacer()
         }
     }
 }

+ 28 - 4
Platform/Shared/VMRemovableDrivesView.swift

@@ -24,6 +24,8 @@ struct VMRemovableDrivesView: View {
     @ObservedObject private var sessionConfig: UTMViewState
     @State private var shareDirectoryFileImportPresented: Bool = false
     @State private var diskImageFileImportPresented: Bool = false
+    /// Explanation see "SwiftUI FileImporter modal bug" in the `body`
+    @State private var workaroundFileImporterBug: Bool = false
     @State private var currentDrive: UTMDrive?
     
     var fileManager: FileManager {
@@ -69,10 +71,32 @@ struct VMRemovableDrivesView: View {
                         // Browse button
                         Button(action: {
                             currentDrive = drive
-                            diskImageFileImportPresented.toggle()
+                            // MARK: SwiftUI FileImporter modal bug
+                            /// At this point in the execution, `diskImageFileImportPresented` must be `false`.
+                            /// However there is a SwiftUI FileImporter modal bug:
+                            /// if the user taps outside the import modal to cancel instead of tapping the actual cancel button,
+                            /// the `.fileImporter` doesn't actually set the isPresented Binding to `false`.
+                            if (diskImageFileImportPresented) {
+                                /// bug! Let's set the bool to false ourselves.
+                                diskImageFileImportPresented = false
+                                /// One more thing: we can't immediately set it to `true` again because then the state won't have changed.
+                                /// So we have to use the workaround, which is caught in the `.onChange` below.
+                                workaroundFileImporterBug = true
+                            } else {
+                                diskImageFileImportPresented = true
+                            }
                         }, label: {
                             Label("Browse", systemImage: "doc.badge.plus")
                         })
+                        .onChange(of: workaroundFileImporterBug) { doWorkaround in
+                            /// Explanation see "SwiftUI FileImporter modal bug" above
+                            if doWorkaround {
+                                DispatchQueue.main.async {
+                                    workaroundFileImporterBug = false
+                                    diskImageFileImportPresented = true
+                                }
+                            }
+                        }
                         // Eject button
                         if drive.status != .ejected {
                             Button(action: { clearRemovableImage(forDrive: drive) }, label: {
@@ -180,9 +204,9 @@ struct VMRemovableDrivesView_Previews: PreviewProvider {
         VMRemovableDrivesView(vm: UTMVirtualMachine(configuration: config, withDestinationURL: URL(fileURLWithPath: "")))
         .onAppear {
             config.shareDirectoryEnabled = true
-            config.newDrive("", type: .disk, interface: "ide")
-            config.newDrive("", type: .disk, interface: "sata")
-            config.newDrive("", type: .CD, interface: "ide")
+            config.newDrive("", path: "", type: .disk, interface: "ide")
+            config.newDrive("", path: "", type: .disk, interface: "sata")
+            config.newDrive("", path: "", type: .CD, interface: "ide")
         }
     }
 }

+ 36 - 13
Platform/UTMData.swift

@@ -131,7 +131,7 @@ class UTMData: ObservableObject {
         return ProcessInfo.processInfo.globallyUniqueString
     }
     
-    func newDefaultDriveName(type: UTMDiskImageType, forConfig: UTMConfiguration) -> String {
+    func newDefaultDrivePath(type: UTMDiskImageType, forConfig: UTMConfiguration) -> String {
         let nameForId = { (i: Int) in "\(type.description)-\(i).qcow2" }
         for i in 0..<1000 {
             let name = nameForId(i)
@@ -143,6 +143,27 @@ class UTMData: ObservableObject {
         return UUID().uuidString
     }
     
+    func newDefaultDriveName(for config: UTMConfiguration) -> String {
+        let nameForId = { (i: Int) in "drive\(i)" }
+        for i in 0..<1000 {
+            let name = nameForId(i)
+            var free: Bool = true
+            for j in 0..<config.countDrives {
+                guard let taken = config.driveName(for: j) else {
+                    continue
+                }
+                if taken == name {
+                    free = false
+                    break
+                }
+            }
+            if free {
+                return name
+            }
+        }
+        return UUID().uuidString
+    }
+    
     // MARK: - VM functions
     
     func save(vm: UTMVirtualMachine) throws {
@@ -348,10 +369,10 @@ class UTMData: ObservableObject {
             }
         }
         
-        let name = drive.lastPathComponent
+        let path = drive.lastPathComponent
         let imageType: UTMDiskImageType = drive.pathExtension.lowercased() == "iso" ? .CD : .disk
         let imagesPath = config.imagesPath
-        let dstPath = imagesPath.appendingPathComponent(name)
+        let dstPath = imagesPath.appendingPathComponent(path)
         if !fileManager.fileExists(atPath: imagesPath.path) {
             try fileManager.createDirectory(at: imagesPath, withIntermediateDirectories: false, attributes: nil)
         }
@@ -361,25 +382,26 @@ class UTMData: ObservableObject {
             try fileManager.moveItem(at: drive, to: dstPath)
         }
         DispatchQueue.main.async {
+            let name = self.newDefaultDriveName(for: config)
             let interface: String
             if let target = config.systemTarget {
                 interface = UTMConfiguration.defaultDriveInterface(forTarget: target, type: imageType)
             } else {
                 interface = "none"
             }
-            config.newDrive(name, type: imageType, interface: interface)
+            config.newDrive(name, path: path, type: imageType, interface: interface)
         }
     }
     
     func createDrive(_ drive: VMDriveImage, for config: UTMConfiguration) throws {
-        var name: String = ""
+        var path: String = ""
         if !drive.removable {
             guard drive.size > 0 else {
                 throw NSLocalizedString("Invalid drive size.", comment: "UTMData")
             }
-            name = newDefaultDriveName(type: drive.imageType, forConfig: config)
+            path = newDefaultDrivePath(type: drive.imageType, forConfig: config)
             let imagesPath = config.imagesPath
-            let dstPath = imagesPath.appendingPathComponent(name)
+            let dstPath = imagesPath.appendingPathComponent(path)
             if !fileManager.fileExists(atPath: imagesPath.path) {
                 try fileManager.createDirectory(at: imagesPath, withIntermediateDirectories: false, attributes: nil)
             }
@@ -391,20 +413,21 @@ class UTMData: ObservableObject {
         }
         
         DispatchQueue.main.async {
+            let name = self.newDefaultDriveName(for: config)
             let interface = drive.interface ?? "none"
             if drive.removable {
-                config.newRemovableDrive(drive.imageType, interface: interface)
+                config.newRemovableDrive(name, type: drive.imageType, interface: interface)
             } else {
-                config.newDrive(name, type: drive.imageType, interface: interface)
+                config.newDrive(name, path: path, type: drive.imageType, interface: interface)
             }
         }
     }
     
     func removeDrive(at index: Int, for config: UTMConfiguration) throws {
-        if let name = config.driveImagePath(for: index) {
-            let path = config.imagesPath.appendingPathComponent(name);
-            if fileManager.fileExists(atPath: path.path) {
-                try fileManager.removeItem(at: path)
+        if let path = config.driveImagePath(for: index) {
+            let fullPath = config.imagesPath.appendingPathComponent(path);
+            if fileManager.fileExists(atPath: fullPath.path) {
+                try fileManager.removeItem(at: fullPath)
             }
         }
         

+ 3 - 2
Platform/iOS/Legacy/VMConfigDriveDetailViewController.m

@@ -160,11 +160,12 @@
         return;
     }
     if (!self.existing) {
+        NSString *name = [NSUUID UUID].UUIDString;
         self.existing = YES;
         if (self.removable) {
-            self.driveIndex = [self.configuration newRemovableDrive:self.imageType interface:self.driveInterfaceType];
+            self.driveIndex = [self.configuration newRemovableDrive:name type:self.imageType interface:self.driveInterfaceType];
         } else {
-            self.driveIndex = [self.configuration newDrive:self.imageName type:self.imageType interface:self.driveInterfaceType];
+            self.driveIndex = [self.configuration newDrive:name path:self.imageName type:self.imageType interface:self.driveInterfaceType];
         }
     } else {
         [self.configuration setDriveRemovable:self.removable forIndex:self.driveIndex];

+ 2 - 2
Platform/iOS/VMConfigDrivesView.swift

@@ -174,8 +174,8 @@ struct VMConfigDrivesView_Previews: PreviewProvider {
             }
         }.onAppear {
             if config.countDrives == 0 {
-                config.newDrive("test.img", type: .disk, interface: "ide")
-                config.newDrive("bios.bin", type: .BIOS, interface: "none")
+                config.newDrive("", path: "test.img", type: .disk, interface: "ide")
+                config.newDrive("", path: "bios.bin", type: .BIOS, interface: "none")
             }
         }
     }

+ 4 - 0
Platform/macOS/Display/VMDisplayMetalWindowController.swift

@@ -423,6 +423,10 @@ extension VMDisplayMetalWindowController: VMMetalViewInputDelegate {
                     return true // do not close window when in progress
                 }
             }
+        } else if event.modifierFlags.contains(.command) && event.type == .keyUp {
+            // for some reason, macOS doesn't like to send Cmd+KeyUp
+            metalView.keyUp(with: event)
+            return true
         }
         return false
     }

+ 2 - 2
Platform/macOS/VMConfigDrivesView.swift

@@ -148,8 +148,8 @@ struct VMConfigDrivesView_Previews: PreviewProvider {
             VMConfigDrivesView(config: config)
         }.onAppear {
             if config.countDrives == 0 {
-                config.newDrive("test.img", type: .disk, interface: "ide")
-                config.newDrive("bios.bin", type: .BIOS, interface: "none")
+                config.newDrive("drive0", path: "test.img", type: .disk, interface: "ide")
+                config.newDrive("drive1", path: "bios.bin", type: .BIOS, interface: "none")
             }
         }
     }

+ 1 - 0
UTM.xcodeproj/project.pbxproj

@@ -4225,6 +4225,7 @@
 				);
 				MACOSX_DEPLOYMENT_TARGET = 11.0;
 				PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_PREFIX:default=com.utmapp).UTM";
+				MARKETING_VERSION = 2.0.28;
 				PRODUCT_NAME = "$(PROJECT_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "$(PROVISIONING_PROFILE_SPECIFIER_MAC:default=)";
 				SDKROOT = macosx;