Browse Source

vm: refactor QEMU backend to subclasses

osy 4 years ago
parent
commit
1e3691d04a
100 changed files with 1657 additions and 1250 deletions
  1. 1 30
      Configuration/UTMConfiguration+Drives.h
  2. 1 228
      Configuration/UTMConfiguration+Drives.m
  3. 11 7
      Configuration/UTMConfiguration.h
  4. 11 107
      Configuration/UTMConfiguration.m
  5. 3 5
      Configuration/UTMQemuConfiguration+Constants.h
  6. 15 23
      Configuration/UTMQemuConfiguration+Constants.m
  7. 2 2
      Configuration/UTMQemuConfiguration+ConstantsGenerated.m
  8. 3 3
      Configuration/UTMQemuConfiguration+Defaults.h
  9. 11 11
      Configuration/UTMQemuConfiguration+Defaults.m
  10. 2 2
      Configuration/UTMQemuConfiguration+Display.h
  11. 3 3
      Configuration/UTMQemuConfiguration+Display.m
  12. 57 0
      Configuration/UTMQemuConfiguration+Drives.h
  13. 248 0
      Configuration/UTMQemuConfiguration+Drives.m
  14. 2 2
      Configuration/UTMQemuConfiguration+Miscellaneous.h
  15. 4 4
      Configuration/UTMQemuConfiguration+Miscellaneous.m
  16. 7 7
      Configuration/UTMQemuConfiguration+Networking.h
  17. 10 10
      Configuration/UTMQemuConfiguration+Networking.m
  18. 2 2
      Configuration/UTMQemuConfiguration+Sharing.h
  19. 4 4
      Configuration/UTMQemuConfiguration+Sharing.m
  20. 2 2
      Configuration/UTMQemuConfiguration+System.h
  21. 11 11
      Configuration/UTMQemuConfiguration+System.m
  22. 34 0
      Configuration/UTMQemuConfiguration.h
  23. 151 0
      Configuration/UTMQemuConfiguration.m
  24. 3 3
      Configuration/UTMQemuConfigurationDelegate.h
  25. 28 14
      Configuration/UTMQemuConfigurationExtension.swift
  26. 1 1
      Configuration/UTMQemuConfigurationPortForward.h
  27. 2 2
      Configuration/UTMQemuConfigurationPortForward.m
  28. 1 1
      Managers/UTMDrive.h
  29. 2 2
      Managers/UTMDrive.m
  30. 1 1
      Managers/UTMQemu.h
  31. 2 2
      Managers/UTMQemuSystem.h
  32. 12 12
      Managers/UTMQemuSystem.m
  33. 2 2
      Managers/UTMQemuVirtualMachine+Drives.h
  34. 9 9
      Managers/UTMQemuVirtualMachine+Drives.m
  35. 2 2
      Managers/UTMQemuVirtualMachine+SPICE.h
  36. 10 10
      Managers/UTMQemuVirtualMachine+SPICE.m
  37. 2 2
      Managers/UTMQemuVirtualMachine+Terminal.h
  38. 3 3
      Managers/UTMQemuVirtualMachine+Terminal.m
  39. 38 0
      Managers/UTMQemuVirtualMachine.h
  40. 506 0
      Managers/UTMQemuVirtualMachine.m
  41. 3 3
      Managers/UTMSpiceIO.h
  42. 4 4
      Managers/UTMSpiceIO.m
  43. 2 2
      Managers/UTMTerminalIO.h
  44. 2 2
      Managers/UTMTerminalIO.m
  45. 38 0
      Managers/UTMVirtualMachine+IO.h
  46. 21 0
      Managers/UTMVirtualMachine+IO.m
  47. 54 0
      Managers/UTMVirtualMachine-Private.h
  48. 4 17
      Managers/UTMVirtualMachine.h
  49. 44 440
      Managers/UTMVirtualMachine.m
  50. 1 3
      Managers/UTMVirtualMachineExtension.swift
  51. 2 2
      Platform/Shared/ContentView.swift
  52. 6 6
      Platform/Shared/VMCardView.swift
  53. 8 8
      Platform/Shared/VMConfigDisplayView.swift
  54. 1 1
      Platform/Shared/VMConfigDriveCreateView.swift
  55. 4 4
      Platform/Shared/VMConfigDriveDetailsView.swift
  56. 2 2
      Platform/Shared/VMConfigInfoView.swift
  57. 2 2
      Platform/Shared/VMConfigInputView.swift
  58. 7 7
      Platform/Shared/VMConfigNetworkView.swift
  59. 4 4
      Platform/Shared/VMConfigPortForwardForm.swift
  60. 4 4
      Platform/Shared/VMConfigQEMUView.swift
  61. 2 2
      Platform/Shared/VMConfigSharingView.swift
  62. 4 4
      Platform/Shared/VMConfigSoundView.swift
  63. 14 14
      Platform/Shared/VMConfigSystemView.swift
  64. 6 6
      Platform/Shared/VMDetailsView.swift
  65. 1 1
      Platform/Shared/VMDriveImage.swift
  66. 2 2
      Platform/Shared/VMRemovableDrivesView.swift
  67. 15 11
      Platform/Swift-Bridging-Header.h
  68. 15 15
      Platform/UTMData.swift
  69. 2 2
      Platform/iOS/Display/VMDisplayMetalViewController+Gamepad.m
  70. 5 5
      Platform/iOS/Display/VMDisplayMetalViewController+Pointer.m
  71. 9 9
      Platform/iOS/Display/VMDisplayMetalViewController+Touch.m
  72. 7 7
      Platform/iOS/Display/VMDisplayMetalViewController.m
  73. 1 1
      Platform/iOS/Display/VMDisplayTerminalViewController+Keyboard.m
  74. 7 7
      Platform/iOS/Display/VMDisplayTerminalViewController.m
  75. 4 2
      Platform/iOS/Display/VMDisplayViewController.h
  76. 4 0
      Platform/iOS/Display/VMDisplayViewController.m
  77. 1 1
      Platform/iOS/Legacy/VMConfigCreateViewController.m
  78. 3 3
      Platform/iOS/Legacy/VMConfigDisplayViewController.m
  79. 1 1
      Platform/iOS/Legacy/VMConfigDriveDetailViewController.h
  80. 10 10
      Platform/iOS/Legacy/VMConfigDriveDetailViewController.m
  81. 2 2
      Platform/iOS/Legacy/VMConfigDrivePickerViewController.h
  82. 4 4
      Platform/iOS/Legacy/VMConfigDrivePickerViewController.m
  83. 2 2
      Platform/iOS/Legacy/VMConfigDrivesViewController.h
  84. 5 5
      Platform/iOS/Legacy/VMConfigDrivesViewController.m
  85. 4 4
      Platform/iOS/Legacy/VMConfigExistingViewController.m
  86. 1 1
      Platform/iOS/Legacy/VMConfigInputViewController.m
  87. 2 2
      Platform/iOS/Legacy/VMConfigPortForwardingViewController.h
  88. 6 6
      Platform/iOS/Legacy/VMConfigPortForwardingViewController.m
  89. 2 2
      Platform/iOS/Legacy/VMConfigSharingViewController.m
  90. 1 1
      Platform/iOS/Legacy/VMConfigSoundViewController.m
  91. 3 3
      Platform/iOS/Legacy/VMConfigSystemArgumentsViewController.h
  92. 1 1
      Platform/iOS/Legacy/VMConfigSystemArgumentsViewController.m
  93. 7 7
      Platform/iOS/Legacy/VMConfigSystemViewController.m
  94. 2 2
      Platform/iOS/Legacy/VMConfigViewController.h
  95. 8 8
      Platform/iOS/Legacy/VMConfigViewController.m
  96. 34 34
      Platform/iOS/Legacy/VMListViewController.m
  97. 4 3
      Platform/iOS/UTMDataExtension.swift
  98. 2 2
      Platform/iOS/VMConfigDrivesView.swift
  99. 8 8
      Platform/iOS/VMConfigNetworkPortForwardView.swift
  100. 14 10
      Platform/iOS/VMSettingsView.swift

+ 1 - 30
Configuration/UTMConfiguration+Drives.h

@@ -1,5 +1,5 @@
 //
-// Copyright © 2020 osy. All rights reserved.
+// Copyright © 2021 osy. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,44 +16,15 @@
 
 #import "UTMConfiguration.h"
 
-typedef NS_ENUM(NSInteger, UTMDiskImageType) {
-    UTMDiskImageTypeNone,
-    UTMDiskImageTypeDisk,
-    UTMDiskImageTypeCD,
-    UTMDiskImageTypeBIOS,
-    UTMDiskImageTypeKernel,
-    UTMDiskImageTypeInitrd,
-    UTMDiskImageTypeDTB,
-    UTMDiskImageTypeMax
-};
-
 NS_ASSUME_NONNULL_BEGIN
 
 @interface UTMConfiguration (Drives)
 
 @property (nonatomic, readonly) NSURL *imagesPath;
-@property (nonatomic, readonly) NSInteger countDrives;
 @property (nonatomic, nullable, readonly) NSArray<NSString *> *orphanedDrives;
 
-- (void)migrateDriveConfigurationIfNecessary;
 - (void)recoverOrphanedDrives;
 
-- (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;
-- (void)setDriveInterfaceType:(NSString *)interfaceType forIndex:(NSInteger)index;
-- (UTMDiskImageType)driveImageTypeForIndex:(NSInteger)index;
-- (void)setDriveImageType:(UTMDiskImageType)type forIndex:(NSInteger)index;
-- (BOOL)driveRemovableForIndex:(NSInteger)index;
-- (void)setDriveRemovable:(BOOL)isRemovable forIndex:(NSInteger)index;
-- (void)moveDriveIndex:(NSInteger)index to:(NSInteger)newIndex;
-- (void)removeDriveAtIndex:(NSInteger)index;
-- (NSString *)driveLabelForIndex:(NSInteger)index;
-
 @end
 
 NS_ASSUME_NONNULL_END

+ 1 - 228
Configuration/UTMConfiguration+Drives.m

@@ -1,5 +1,5 @@
 //
-// Copyright © 2020 osy. All rights reserved.
+// Copyright © 2021 osy. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,235 +14,8 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Constants.h"
 #import "UTMConfiguration+Drives.h"
-#import "UTM-Swift.h"
-
-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";
-static const NSString *const kUTMConfigRemovableKey = @"Removable";
-static const NSString *const kUTMConfigCdromKey = @"Cdrom";
-
-@interface UTMConfiguration ()
-
-@property (nonatomic, readonly) NSMutableDictionary *rootDict;
-
-@end
 
 @implementation UTMConfiguration (Drives)
 
-#pragma mark - Images Path
-
-- (NSURL *)imagesPath {
-    if (self.existingPath) {
-        return [self.existingPath URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
-    } else {
-        return [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
-    }
-}
-
-#pragma mark - Migration
-
-- (void)migrateDriveConfigurationIfNecessary {
-    // Migrate Cdrom => ImageType
-    [self.rootDict[kUTMConfigDrivesKey] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
-        if (!obj[kUTMConfigImageTypeKey]) {
-            if ([obj[kUTMConfigCdromKey] boolValue]) {
-                [self setDriveImageType:UTMDiskImageTypeCD forIndex:idx];
-            } else {
-                [self setDriveImageType:UTMDiskImageTypeDisk forIndex:idx];
-            }
-            [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
-
-// ensure user can delete the drive image from the interface if something wrong happens
-- (NSArray<NSString *> *)orphanedDrives {
-    NSFileManager *manager = [NSFileManager defaultManager];
-    NSArray<NSURL *> *files = [manager contentsOfDirectoryAtURL:self.imagesPath includingPropertiesForKeys:nil options:0 error:nil];
-    if (files.count == 0) {
-        return nil; // empty or does not exist
-    }
-    // find existing drives
-    NSMutableSet<NSString *> *existing = [NSMutableSet set];
-    for (NSInteger i = 0; i < self.countDrives; i++) {
-        if (![self driveRemovableForIndex:i]) {
-            [existing addObject:[self driveImagePathForIndex:i]];
-        }
-    }
-    // add any missing drives
-    NSMutableArray<NSString *> *orphans = [NSMutableArray array];
-    for (NSURL *file in files) {
-        NSString *name = [file lastPathComponent];
-        if (![existing containsObject:name]) {
-            [orphans addObject:name];
-        }
-    }
-    return orphans;
-}
-
-- (void)recoverOrphanedDrives {
-    NSArray<NSString *> *orphans = self.orphanedDrives;
-    for (NSInteger i = 0; i < orphans.count; i++) {
-        NSString *name = [NSUUID UUID].UUIDString;
-        [self newDrive:name path:orphans[i] type:UTMDiskImageTypeNone interface:@""];
-    }
-}
-
-#pragma mark - Drives array handling
-
-- (NSInteger)countDrives {
-    return [self.rootDict[kUTMConfigDrivesKey] count];
-}
-
-- (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:@{
-        kUTMConfigDriveNameKey: name,
-        kUTMConfigImagePathKey: path,
-        kUTMConfigImageTypeKey: strType,
-        kUTMConfigInterfaceTypeKey: interface
-    }];
-    [self propertyWillChange];
-    [self.rootDict[kUTMConfigDrivesKey] addObject:drive];
-    return index;
-}
-
-- (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
-    }];
-    [self propertyWillChange];
-    [self.rootDict[kUTMConfigDrivesKey] addObject:drive];
-    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;
-    } else {
-        return self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImagePathKey];
-    }
-}
-
-- (void)setImagePath:(NSString *)path forIndex:(NSInteger)index {
-    [self propertyWillChange];
-    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImagePathKey] = path;
-}
-
-- (nullable NSString *)driveInterfaceTypeForIndex:(NSInteger)index {
-    if (index >= self.countDrives) {
-        return nil;
-    } else {
-        return self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigInterfaceTypeKey];
-    }
-}
-
-- (void)setDriveInterfaceType:(NSString *)interfaceType forIndex:(NSInteger)index {
-    [self propertyWillChange];
-    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigInterfaceTypeKey] = interfaceType;
-}
-
-- (UTMDiskImageType)driveImageTypeForIndex:(NSInteger)index {
-    if (index >= self.countDrives) {
-        return UTMDiskImageTypeDisk;
-    }
-    NSString *strType = self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImageTypeKey];
-    NSInteger type = [[UTMConfiguration supportedImageTypes] indexOfObject:strType];
-    if (type == NSNotFound || type >= UTMDiskImageTypeMax) {
-        return UTMDiskImageTypeDisk;
-    } else {
-        return (UTMDiskImageType)type;
-    }
-}
-
-- (void)setDriveImageType:(UTMDiskImageType)type forIndex:(NSInteger)index {
-    NSString *strType = [UTMConfiguration supportedImageTypes][type];
-    [self propertyWillChange];
-    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImageTypeKey] = strType;
-}
-
-- (BOOL)driveRemovableForIndex:(NSInteger)index {
-    if (index >= self.countDrives) {
-        return NO;
-    } else {
-        return [self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigRemovableKey] boolValue];
-    }
-}
-
-- (void)setDriveRemovable:(BOOL)isRemovable forIndex:(NSInteger)index {
-    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigRemovableKey] = @(isRemovable);
-    if (isRemovable) {
-        [self.rootDict[kUTMConfigDrivesKey][index] removeObjectForKey:kUTMConfigImagePathKey];
-    }
-    [self propertyWillChange];
-}
-
-- (void)moveDriveIndex:(NSInteger)index to:(NSInteger)newIndex {
-    NSMutableDictionary *drive = self.rootDict[kUTMConfigDrivesKey][index];
-    [self propertyWillChange];
-    [self.rootDict[kUTMConfigDrivesKey] removeObjectAtIndex:index];
-    [self.rootDict[kUTMConfigDrivesKey] insertObject:drive atIndex:newIndex];
-}
-
-- (void)removeDriveAtIndex:(NSInteger)index {
-    [self propertyWillChange];
-    [self.rootDict[kUTMConfigDrivesKey] removeObjectAtIndex:index];
-}
-
-- (NSString *)driveLabelForIndex:(NSInteger)index {
-    NSArray<NSString *> *interfaces = [UTMConfiguration supportedDriveInterfaces];
-    NSArray<NSString *> *interfacesPretty = [UTMConfiguration supportedDriveInterfacesPretty];
-    NSString *interface = [self driveInterfaceTypeForIndex:index];
-    NSInteger interfaceIndex = NSNotFound;
-    if (interface) {
-        interfaceIndex = [interfaces indexOfObjectPassingTest:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
-            return [interface isEqualToString:obj];
-        }];
-    }
-    if (interfaceIndex == NSNotFound) {
-        interfaceIndex = interfacesPretty.count - 1;
-    }
-    return [NSString stringWithFormat:NSLocalizedString(@"%@ Drive", @"UTMConfiguration+Drives"), interfacesPretty[interfaceIndex]];
-}
-
 @end

+ 11 - 7
Configuration/UTMConfiguration.h

@@ -1,5 +1,5 @@
 //
-// Copyright © 2019 osy. All rights reserved.
+// Copyright © 2021 osy. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -27,13 +27,17 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, nullable, copy) NSURL *selectedCustomIconPath;
 @property (nonatomic, nullable, copy) NSNumber *version;
 
-- (void)migrateConfigurationIfNecessary;
-- (instancetype)init NS_DESIGNATED_INITIALIZER;
-- (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path NS_DESIGNATED_INITIALIZER;
+@property (nonatomic, readonly) NSString *subtitle;
+@property (nonatomic, readonly) NSString *uuid;
 
-- (NSURL*)terminalInputOutputURL;
-- (void)resetDefaults;
-- (void)reloadConfigurationWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path;
+@property (nonatomic, assign) BOOL shareDirectoryEnabled;
+
+@property (nonatomic, nullable, copy) NSString *icon;
+@property (nonatomic, assign) BOOL iconCustom;
+@property (nonatomic, nullable, copy) NSString *notes;
+
++ (NSString *)diskImagesDirectory;
++ (NSString *)debugLogName;
 
 @end
 

+ 11 - 107
Configuration/UTMConfiguration.m

@@ -1,5 +1,5 @@
 //
-// Copyright © 2019 osy. All rights reserved.
+// Copyright © 2021 osy. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,119 +15,32 @@
 //
 
 #import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Defaults.h"
-#import "UTMConfiguration+Display.h"
-#import "UTMConfiguration+Drives.h"
-#import "UTMConfiguration+Miscellaneous.h"
-#import "UTMConfiguration+Networking.h"
-#import "UTMConfiguration+Sharing.h"
-#import "UTMConfiguration+System.h"
 #import "UTM-Swift.h"
 
-const NSString *const kUTMConfigSystemKey = @"System";
-const NSString *const kUTMConfigDisplayKey = @"Display";
-const NSString *const kUTMConfigInputKey = @"Input";
-const NSString *const kUTMConfigNetworkingKey = @"Networking";
-const NSString *const kUTMConfigPrintingKey = @"Printing";
-const NSString *const kUTMConfigSoundKey = @"Sound";
-const NSString *const kUTMConfigSharingKey = @"Sharing";
-const NSString *const kUTMConfigDrivesKey = @"Drives";
-const NSString *const kUTMConfigDebugKey = @"Debug";
-const NSString *const kUTMConfigInfoKey = @"Info";
-const NSString *const kUTMConfigVersionKey = @"ConfigurationVersion";
-
-const NSInteger kCurrentConfigurationVersion = 2;
-
 @interface UTMConfiguration ()
 
-@property (nonatomic, readonly) NSMutableDictionary *rootDict;
+@property (nonatomic, readwrite) NSString *uuid;
 
 @end
 
-@implementation UTMConfiguration {
-    NSMutableDictionary *_rootDict;
-}
-
-@synthesize rootDict = _rootDict;
+@implementation UTMConfiguration
 
-#pragma mark - Migration
-
-- (void)migrateConfigurationIfNecessary {
-    [self migrateMiscellaneousConfigurationIfNecessary];
-    [self migrateDriveConfigurationIfNecessary];
-    [self migrateNetworkConfigurationIfNecessary];
-    [self migrateSystemConfigurationIfNecessary];
-    [self migrateDisplayConfigurationIfNecessary];
-    [self migrateSharingConfigurationIfNecessary];
-    self.version = @(kCurrentConfigurationVersion);
++ (NSString *)diskImagesDirectory {
+    return @"Images";
 }
 
-#pragma mark - Initialization
-
-- (instancetype)init {
-    self = [super init];
-    if (self) {
-        [self resetDefaults];
-    }
-    return self;
++ (NSString *)debugLogName {
+    return @"debug.log";
 }
 
-- (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path {
-    self = [super init];
-    if (self) {
-        [self reloadConfigurationWithDictionary:dictionary name:name path:path];
+- (instancetype)init {
+    if (self = [super init]) {
+        self.uuid = [[NSUUID UUID] UUIDString];
     }
     return self;
 }
 
-#pragma mark - Dictionary representation
-
-- (NSDictionary *)dictRepresentation {
-    return (NSDictionary *)_rootDict;
-}
-
-- (NSURL*)terminalInputOutputURL {
-    NSURL* tmpDir = [[NSFileManager defaultManager] temporaryDirectory];
-    NSString* ioFileName = [NSString stringWithFormat: @"%@.terminal", self.name];
-    NSURL* ioFile = [tmpDir URLByAppendingPathComponent: ioFileName];
-    return ioFile;
-}
-
-- (void)resetDefaults {
-    [self propertyWillChange];
-    _rootDict = [@{
-        kUTMConfigSystemKey: [NSMutableDictionary new],
-        kUTMConfigDisplayKey: [NSMutableDictionary new],
-        kUTMConfigInputKey: [NSMutableDictionary new],
-        kUTMConfigNetworkingKey: [NSMutableDictionary new],
-        kUTMConfigPrintingKey: [NSMutableDictionary new],
-        kUTMConfigSoundKey: [NSMutableDictionary new],
-        kUTMConfigSharingKey: [NSMutableDictionary new],
-        kUTMConfigDrivesKey: [NSMutableArray new],
-        kUTMConfigDebugKey: [NSMutableDictionary new],
-        kUTMConfigInfoKey: [NSMutableDictionary new],
-    } mutableCopy];
-    self.version = @(kCurrentConfigurationVersion);
-    [self loadDefaults];
-}
-
-- (void)reloadConfigurationWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path {
-    [self propertyWillChange];
-    _rootDict = CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)dictionary, kCFPropertyListMutableContainers));
-    self.name = name;
-    self.existingPath = path;
-    self.selectedCustomIconPath = nil;
-    [self migrateConfigurationIfNecessary];
-}
-
-#pragma mark - NSCopying
-
-- (id)copyWithZone:(NSZone *)zone {
-    return [[UTMConfiguration alloc] initWithDictionary:_rootDict name:_name path:_existingPath];
-}
-
-#pragma mark - Settings
+#pragma mark - Properties
 
 - (void)setName:(NSString *)name {
     [self propertyWillChange];
@@ -144,13 +57,4 @@ const NSInteger kCurrentConfigurationVersion = 2;
     _selectedCustomIconPath = selectedCustomIconPath;
 }
 
-- (void)setVersion:(NSNumber *)version {
-    [self propertyWillChange];
-    self.rootDict[kUTMConfigVersionKey] = version;
-}
-
-- (NSNumber *)version {
-    return self.rootDict[kUTMConfigVersionKey];
-}
-
 @end

+ 3 - 5
Configuration/UTMConfiguration+Constants.h → Configuration/UTMQemuConfiguration+Constants.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Constants)
+@interface UTMQemuConfiguration (Constants)
 
 + (NSArray<NSString *>*)supportedOptions:(NSString *)key pretty:(BOOL)pretty;
 + (NSArray<NSString *>*)supportedBootDevicesPretty;
@@ -34,12 +34,10 @@ NS_ASSUME_NONNULL_BEGIN
 + (NSArray<NSString *>*)supportedConsoleFonts API_AVAILABLE(ios(11));
 + (NSArray<NSString *>*)supportedNetworkModes;
 + (NSArray<NSString *>*)supportedNetworkModesPretty;
-+ (NSString *)diskImagesDirectory;
-+ (NSString *)debugLogName;
 
 @end
 
-@interface UTMConfiguration (ConstantsGenerated)
+@interface UTMQemuConfiguration (ConstantsGenerated)
 
 + (NSArray<NSString *>*)supportedArchitectures;
 + (NSArray<NSString *>*)supportedArchitecturesPretty;

+ 15 - 23
Configuration/UTMConfiguration+Constants.m → Configuration/UTMQemuConfiguration+Constants.m

@@ -18,9 +18,9 @@
 #if !TARGET_OS_OSX
 #import <UIKit/UIKit.h>
 #endif
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Constants.h"
 
-@implementation UTMConfiguration (Constants)
+@implementation UTMQemuConfiguration (Constants)
 
 #pragma mark - Constant supported values
 
@@ -95,13 +95,13 @@
 
 + (NSArray<NSString *>*)supportedImageTypesPretty {
     return @[
-             NSLocalizedString(@"None", "UTMConfiguration"),
-             NSLocalizedString(@"Disk Image", "UTMConfiguration"),
-             NSLocalizedString(@"CD/DVD (ISO) Image", "UTMConfiguration"),
-             NSLocalizedString(@"BIOS", "UTMConfiguration"),
-             NSLocalizedString(@"Linux Kernel", "UTMConfiguration"),
-             NSLocalizedString(@"Linux RAM Disk", "UTMConfiguration"),
-             NSLocalizedString(@"Linux Device Tree Binary", "UTMConfiguration")
+             NSLocalizedString(@"None", "UTMQemuConfiguration"),
+             NSLocalizedString(@"Disk Image", "UTMQemuConfiguration"),
+             NSLocalizedString(@"CD/DVD (ISO) Image", "UTMQemuConfiguration"),
+             NSLocalizedString(@"BIOS", "UTMQemuConfiguration"),
+             NSLocalizedString(@"Linux Kernel", "UTMQemuConfiguration"),
+             NSLocalizedString(@"Linux RAM Disk", "UTMQemuConfiguration"),
+             NSLocalizedString(@"Linux Device Tree Binary", "UTMQemuConfiguration")
              ];
 }
 
@@ -168,8 +168,8 @@
 
 + (NSArray<NSString *>*)supportedScalersPretty {
     return @[
-        NSLocalizedString(@"Linear", "UTMConfiguration"),
-        NSLocalizedString(@"Nearest Neighbor", "UTMConfiguration"),
+        NSLocalizedString(@"Linear", "UTMQemuConfiguration"),
+        NSLocalizedString(@"Nearest Neighbor", "UTMQemuConfiguration"),
     ];
 }
 
@@ -217,19 +217,11 @@
 
 + (NSArray<NSString *> *)supportedNetworkModesPretty {
     return @[
-        NSLocalizedString(@"None", "UTMConfiguration"),
-        NSLocalizedString(@"Emulated VLAN", "UTMConfiguration"),
-        NSLocalizedString(@"Shared Network", "UTMConfiguration"),
-        NSLocalizedString(@"Bridged (Advanced)", "UTMConfiguration"),
+        NSLocalizedString(@"None", "UTMQemuConfiguration"),
+        NSLocalizedString(@"Emulated VLAN", "UTMQemuConfiguration"),
+        NSLocalizedString(@"Shared Network", "UTMQemuConfiguration"),
+        NSLocalizedString(@"Bridged (Advanced)", "UTMQemuConfiguration"),
     ];
 }
 
-+ (NSString *)diskImagesDirectory {
-    return @"Images";
-}
-
-+ (NSString *)debugLogName {
-    return @"debug.log";
-}
-
 @end

+ 2 - 2
Configuration/UTMConfiguration+ConstantsGenerated.m → Configuration/UTMQemuConfiguration+ConstantsGenerated.m

@@ -16,9 +16,9 @@
 
 // !! THIS FILE IS GENERATED FROM const-gen.py, DO NOT MODIFY MANUALLY !!
 
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Constants.h"
 
-@implementation UTMConfiguration (ConstantsGenerated)
+@implementation UTMQemuConfiguration (ConstantsGenerated)
 
 + (NSArray<NSString *>*)supportedArchitectures {
     return @[

+ 3 - 3
Configuration/UTMConfiguration+Defaults.h → Configuration/UTMQemuConfiguration+Defaults.h

@@ -14,12 +14,12 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Drives.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Drives.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Defaults)
+@interface UTMQemuConfiguration (Defaults)
 
 - (void)loadDefaults;
 - (void)loadDefaultsForTarget:(nullable NSString *)target architecture:(nullable NSString *)architecture;

+ 11 - 11
Configuration/UTMConfiguration+Defaults.m → Configuration/UTMQemuConfiguration+Defaults.m

@@ -14,20 +14,20 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Defaults.h"
-#import "UTMConfiguration+Display.h"
-#import "UTMConfiguration+Miscellaneous.h"
-#import "UTMConfiguration+Networking.h"
-#import "UTMConfiguration+Sharing.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration+Defaults.h"
+#import "UTMQemuConfiguration+Display.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration+Networking.h"
+#import "UTMQemuConfiguration+Sharing.h"
+#import "UTMQemuConfiguration+System.h"
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 - (NSString *)generateMacAddress;
 
 @end
 
-@implementation UTMConfiguration (Defaults)
+@implementation UTMQemuConfiguration (Defaults)
 
 - (void)loadDefaults {
     self.systemArchitecture = @"x86_64";
@@ -40,7 +40,7 @@
     } else {
         self.systemBootDevice = @"cd";
     }
-    self.systemUUID = [[NSUUID UUID] UUIDString];
+    self.systemUUID = super.uuid;
     self.displayCard = @"qxl-vga";
     self.displayUpscaler = @"linear";
     self.displayDownscaler = @"linear";
@@ -79,12 +79,12 @@
     } else if ([target isEqualToString:@"isapc"]) {
         self.inputLegacy = YES; // no USB support
     }
-    NSString *machineProp = [UTMConfiguration defaultMachinePropertiesForTarget:target];
+    NSString *machineProp = [UTMQemuConfiguration defaultMachinePropertiesForTarget:target];
     if (machineProp) {
         self.systemMachineProperties = machineProp;
     }
     if (target && architecture) {
-        self.systemCPU = [UTMConfiguration defaultCPUForTarget:target architecture:architecture];
+        self.systemCPU = [UTMQemuConfiguration defaultCPUForTarget:target architecture:architecture];
     }
 }
 

+ 2 - 2
Configuration/UTMConfiguration+Display.h → Configuration/UTMQemuConfiguration+Display.h

@@ -14,12 +14,12 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 @import Metal;
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Display)
+@interface UTMQemuConfiguration (Display)
 
 @property (nonatomic, assign) BOOL displayConsoleOnly;
 @property (nonatomic, assign) BOOL displayFitScreen;

+ 3 - 3
Configuration/UTMConfiguration+Display.m → Configuration/UTMQemuConfiguration+Display.m

@@ -14,7 +14,7 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Display.h"
+#import "UTMQemuConfiguration+Display.h"
 #import "UTM-Swift.h"
 
 extern const NSString *const kUTMConfigDisplayKey;
@@ -31,13 +31,13 @@ const NSString *const kUTMConfigConsoleBlinkKey = @"ConsoleBlink";
 const NSString *const kUTMConfigConsoleResizeCommandKey = @"ConsoleResizeCommand";
 const NSString *const kUTMConfigDisplayCardKey = @"DisplayCard";
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 @property (nonatomic, readonly) NSMutableDictionary *rootDict;
 
 @end
 
-@implementation UTMConfiguration (Display)
+@implementation UTMQemuConfiguration (Display)
 
 #pragma mark - Migration
 

+ 57 - 0
Configuration/UTMQemuConfiguration+Drives.h

@@ -0,0 +1,57 @@
+//
+// Copyright © 2020 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMQemuConfiguration.h"
+
+typedef NS_ENUM(NSInteger, UTMDiskImageType) {
+    UTMDiskImageTypeNone,
+    UTMDiskImageTypeDisk,
+    UTMDiskImageTypeCD,
+    UTMDiskImageTypeBIOS,
+    UTMDiskImageTypeKernel,
+    UTMDiskImageTypeInitrd,
+    UTMDiskImageTypeDTB,
+    UTMDiskImageTypeMax
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UTMQemuConfiguration (Drives)
+
+@property (nonatomic, readonly) NSURL *imagesPath;
+@property (nonatomic, readonly) NSInteger countDrives;
+
+- (void)migrateDriveConfigurationIfNecessary;
+
+- (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;
+- (void)setDriveInterfaceType:(NSString *)interfaceType forIndex:(NSInteger)index;
+- (UTMDiskImageType)driveImageTypeForIndex:(NSInteger)index;
+- (void)setDriveImageType:(UTMDiskImageType)type forIndex:(NSInteger)index;
+- (BOOL)driveRemovableForIndex:(NSInteger)index;
+- (void)setDriveRemovable:(BOOL)isRemovable forIndex:(NSInteger)index;
+- (void)moveDriveIndex:(NSInteger)index to:(NSInteger)newIndex;
+- (void)removeDriveAtIndex:(NSInteger)index;
+- (NSString *)driveLabelForIndex:(NSInteger)index;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 248 - 0
Configuration/UTMQemuConfiguration+Drives.m

@@ -0,0 +1,248 @@
+//
+// Copyright © 2020 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Drives.h"
+#import "UTM-Swift.h"
+
+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";
+static const NSString *const kUTMConfigRemovableKey = @"Removable";
+static const NSString *const kUTMConfigCdromKey = @"Cdrom";
+
+@interface UTMQemuConfiguration ()
+
+@property (nonatomic, readonly) NSMutableDictionary *rootDict;
+
+@end
+
+@implementation UTMQemuConfiguration (Drives)
+
+#pragma mark - Images Path
+
+- (NSURL *)imagesPath {
+    if (self.existingPath) {
+        return [self.existingPath URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
+    } else {
+        return [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
+    }
+}
+
+#pragma mark - Migration
+
+- (void)migrateDriveConfigurationIfNecessary {
+    // Migrate Cdrom => ImageType
+    [self.rootDict[kUTMConfigDrivesKey] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+        if (!obj[kUTMConfigImageTypeKey]) {
+            if ([obj[kUTMConfigCdromKey] boolValue]) {
+                [self setDriveImageType:UTMDiskImageTypeCD forIndex:idx];
+            } else {
+                [self setDriveImageType:UTMDiskImageTypeDisk forIndex:idx];
+            }
+            [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
+
+// ensure user can delete the drive image from the interface if something wrong happens
+- (NSArray<NSString *> *)orphanedDrives {
+    NSFileManager *manager = [NSFileManager defaultManager];
+    NSArray<NSURL *> *files = [manager contentsOfDirectoryAtURL:self.imagesPath includingPropertiesForKeys:nil options:0 error:nil];
+    if (files.count == 0) {
+        return nil; // empty or does not exist
+    }
+    // find existing drives
+    NSMutableSet<NSString *> *existing = [NSMutableSet set];
+    for (NSInteger i = 0; i < self.countDrives; i++) {
+        if (![self driveRemovableForIndex:i]) {
+            [existing addObject:[self driveImagePathForIndex:i]];
+        }
+    }
+    // add any missing drives
+    NSMutableArray<NSString *> *orphans = [NSMutableArray array];
+    for (NSURL *file in files) {
+        NSString *name = [file lastPathComponent];
+        if (![existing containsObject:name]) {
+            [orphans addObject:name];
+        }
+    }
+    return orphans;
+}
+
+- (void)recoverOrphanedDrives {
+    NSArray<NSString *> *orphans = self.orphanedDrives;
+    for (NSInteger i = 0; i < orphans.count; i++) {
+        NSString *name = [NSUUID UUID].UUIDString;
+        [self newDrive:name path:orphans[i] type:UTMDiskImageTypeNone interface:@""];
+    }
+}
+
+#pragma mark - Drives array handling
+
+- (NSInteger)countDrives {
+    return [self.rootDict[kUTMConfigDrivesKey] count];
+}
+
+- (NSInteger)newDrive:(NSString *)name path:(NSString *)path type:(UTMDiskImageType)type interface:(NSString *)interface {
+    NSInteger index = [self countDrives];
+    NSString *strType = [UTMQemuConfiguration supportedImageTypes][type];
+    NSMutableDictionary *drive = [[NSMutableDictionary alloc] initWithDictionary:@{
+        kUTMConfigDriveNameKey: name,
+        kUTMConfigImagePathKey: path,
+        kUTMConfigImageTypeKey: strType,
+        kUTMConfigInterfaceTypeKey: interface
+    }];
+    [self propertyWillChange];
+    [self.rootDict[kUTMConfigDrivesKey] addObject:drive];
+    return index;
+}
+
+- (NSInteger)newRemovableDrive:(NSString *)name type:(UTMDiskImageType)type interface:(NSString *)interface {
+    NSInteger index = [self countDrives];
+    NSString *strType = [UTMQemuConfiguration supportedImageTypes][type];
+    NSMutableDictionary *drive = [[NSMutableDictionary alloc] initWithDictionary:@{
+        kUTMConfigDriveNameKey: name,
+        kUTMConfigRemovableKey: @(YES),
+        kUTMConfigImageTypeKey: strType,
+        kUTMConfigInterfaceTypeKey: interface
+    }];
+    [self propertyWillChange];
+    [self.rootDict[kUTMConfigDrivesKey] addObject:drive];
+    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;
+    } else {
+        return self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImagePathKey];
+    }
+}
+
+- (void)setImagePath:(NSString *)path forIndex:(NSInteger)index {
+    [self propertyWillChange];
+    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImagePathKey] = path;
+}
+
+- (nullable NSString *)driveInterfaceTypeForIndex:(NSInteger)index {
+    if (index >= self.countDrives) {
+        return nil;
+    } else {
+        return self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigInterfaceTypeKey];
+    }
+}
+
+- (void)setDriveInterfaceType:(NSString *)interfaceType forIndex:(NSInteger)index {
+    [self propertyWillChange];
+    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigInterfaceTypeKey] = interfaceType;
+}
+
+- (UTMDiskImageType)driveImageTypeForIndex:(NSInteger)index {
+    if (index >= self.countDrives) {
+        return UTMDiskImageTypeDisk;
+    }
+    NSString *strType = self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImageTypeKey];
+    NSInteger type = [[UTMQemuConfiguration supportedImageTypes] indexOfObject:strType];
+    if (type == NSNotFound || type >= UTMDiskImageTypeMax) {
+        return UTMDiskImageTypeDisk;
+    } else {
+        return (UTMDiskImageType)type;
+    }
+}
+
+- (void)setDriveImageType:(UTMDiskImageType)type forIndex:(NSInteger)index {
+    NSString *strType = [UTMQemuConfiguration supportedImageTypes][type];
+    [self propertyWillChange];
+    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigImageTypeKey] = strType;
+}
+
+- (BOOL)driveRemovableForIndex:(NSInteger)index {
+    if (index >= self.countDrives) {
+        return NO;
+    } else {
+        return [self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigRemovableKey] boolValue];
+    }
+}
+
+- (void)setDriveRemovable:(BOOL)isRemovable forIndex:(NSInteger)index {
+    self.rootDict[kUTMConfigDrivesKey][index][kUTMConfigRemovableKey] = @(isRemovable);
+    if (isRemovable) {
+        [self.rootDict[kUTMConfigDrivesKey][index] removeObjectForKey:kUTMConfigImagePathKey];
+    }
+    [self propertyWillChange];
+}
+
+- (void)moveDriveIndex:(NSInteger)index to:(NSInteger)newIndex {
+    NSMutableDictionary *drive = self.rootDict[kUTMConfigDrivesKey][index];
+    [self propertyWillChange];
+    [self.rootDict[kUTMConfigDrivesKey] removeObjectAtIndex:index];
+    [self.rootDict[kUTMConfigDrivesKey] insertObject:drive atIndex:newIndex];
+}
+
+- (void)removeDriveAtIndex:(NSInteger)index {
+    [self propertyWillChange];
+    [self.rootDict[kUTMConfigDrivesKey] removeObjectAtIndex:index];
+}
+
+- (NSString *)driveLabelForIndex:(NSInteger)index {
+    NSArray<NSString *> *interfaces = [UTMQemuConfiguration supportedDriveInterfaces];
+    NSArray<NSString *> *interfacesPretty = [UTMQemuConfiguration supportedDriveInterfacesPretty];
+    NSString *interface = [self driveInterfaceTypeForIndex:index];
+    NSInteger interfaceIndex = NSNotFound;
+    if (interface) {
+        interfaceIndex = [interfaces indexOfObjectPassingTest:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+            return [interface isEqualToString:obj];
+        }];
+    }
+    if (interfaceIndex == NSNotFound) {
+        interfaceIndex = interfacesPretty.count - 1;
+    }
+    return [NSString stringWithFormat:NSLocalizedString(@"%@ Drive", @"UTMQemuConfiguration+Drives"), interfacesPretty[interfaceIndex]];
+}
+
+@end

+ 2 - 2
Configuration/UTMConfiguration+Miscellaneous.h → Configuration/UTMQemuConfiguration+Miscellaneous.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Miscellaneous)
+@interface UTMQemuConfiguration (Miscellaneous)
 
 @property (nonatomic, assign) BOOL inputLegacy;
 @property (nonatomic, assign) BOOL inputScrollInvert;

+ 4 - 4
Configuration/UTMConfiguration+Miscellaneous.m → Configuration/UTMQemuConfiguration+Miscellaneous.m

@@ -14,8 +14,8 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
 #import "UTM-Swift.h"
 
 extern const NSString *const kUTMConfigInputKey;
@@ -38,13 +38,13 @@ const NSString *const kUTMConfigIconKey = @"Icon";
 const NSString *const kUTMConfigIconCustomKey = @"IconCustom";
 const NSString *const kUTMConfigNotesKey = @"Notes";
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 @property (nonatomic, readonly) NSMutableDictionary *rootDict;
 
 @end
 
-@implementation UTMConfiguration (Miscellaneous)
+@implementation UTMQemuConfiguration (Miscellaneous)
 
 #pragma mark - Migration
 

+ 7 - 7
Configuration/UTMConfiguration+Networking.h → Configuration/UTMQemuConfiguration+Networking.h

@@ -14,13 +14,13 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
-@class UTMConfigurationPortForward;
+@class UTMQemuConfigurationPortForward;
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Networking)
+@interface UTMQemuConfiguration (Networking)
 
 @property (nonatomic, assign) BOOL networkEnabled;
 @property (nonatomic, assign) BOOL networkIsolate;
@@ -42,11 +42,11 @@ NS_ASSUME_NONNULL_BEGIN
 
 - (void)migrateNetworkConfigurationIfNecessary;
 
-- (NSInteger)newPortForward:(UTMConfigurationPortForward *)argument;
-- (nullable UTMConfigurationPortForward *)portForwardForIndex:(NSInteger)index;
-- (void)updatePortForwardAtIndex:(NSInteger)index withValue:(UTMConfigurationPortForward *)argument;
+- (NSInteger)newPortForward:(UTMQemuConfigurationPortForward *)argument;
+- (nullable UTMQemuConfigurationPortForward *)portForwardForIndex:(NSInteger)index;
+- (void)updatePortForwardAtIndex:(NSInteger)index withValue:(UTMQemuConfigurationPortForward *)argument;
 - (void)removePortForwardAtIndex:(NSInteger)index;
-- (NSArray<UTMConfigurationPortForward *> *)portForwards;
+- (NSArray<UTMQemuConfigurationPortForward *> *)portForwards;
 
 @end
 

+ 10 - 10
Configuration/UTMConfiguration+Networking.m → Configuration/UTMQemuConfiguration+Networking.m

@@ -14,8 +14,8 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Networking.h"
-#import "UTMConfigurationPortForward.h"
+#import "UTMQemuConfiguration+Networking.h"
+#import "UTMQemuConfigurationPortForward.h"
 #import "UTM-Swift.h"
 
 extern const NSString *const kUTMConfigNetworkingKey;
@@ -44,13 +44,13 @@ static const NSString *const kUTMConfigNetworkPortForwardHostPortKey = @"HostPor
 static const NSString *const kUTMConfigNetworkPortForwardGuestAddressKey = @"GuestAddress";
 static const NSString *const kUTMConfigNetworkPortForwardGuestPortKey = @"GuestPort";
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 @property (nonatomic, readonly) NSMutableDictionary *rootDict;
 
 @end
 
-@implementation UTMConfiguration (Networking)
+@implementation UTMQemuConfiguration (Networking)
 
 #pragma mark - Migration
 
@@ -239,17 +239,17 @@ static const NSString *const kUTMConfigNetworkPortForwardGuestPortKey = @"GuestP
     return [self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey] count];
 }
 
-- (NSInteger)newPortForward:(UTMConfigurationPortForward *)argument {
+- (NSInteger)newPortForward:(UTMQemuConfigurationPortForward *)argument {
     NSInteger index = [self countPortForwards];
     [self updatePortForwardAtIndex:index withValue:argument];
     return index;
 }
 
-- (nullable UTMConfigurationPortForward *)portForwardForIndex:(NSInteger)index {
+- (nullable UTMQemuConfigurationPortForward *)portForwardForIndex:(NSInteger)index {
     NSDictionary *dict = self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey][index];
-    UTMConfigurationPortForward *portForward = nil;
+    UTMQemuConfigurationPortForward *portForward = nil;
     if (dict) {
-        portForward = [[UTMConfigurationPortForward alloc] init];
+        portForward = [[UTMQemuConfigurationPortForward alloc] init];
         portForward.protocol = dict[kUTMConfigNetworkPortForwardProtocolKey];
         portForward.hostAddress = dict[kUTMConfigNetworkPortForwardHostAddressKey];
         portForward.hostPort = dict[kUTMConfigNetworkPortForwardHostPortKey];
@@ -259,7 +259,7 @@ static const NSString *const kUTMConfigNetworkPortForwardGuestPortKey = @"GuestP
     return portForward;
 }
 
-- (void)updatePortForwardAtIndex:(NSInteger)index withValue:(UTMConfigurationPortForward *)argument {
+- (void)updatePortForwardAtIndex:(NSInteger)index withValue:(UTMQemuConfigurationPortForward *)argument {
     if (![self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey] isKindOfClass:[NSMutableArray class]]) {
         self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey] = [NSMutableArray array];
     }
@@ -278,7 +278,7 @@ static const NSString *const kUTMConfigNetworkPortForwardGuestPortKey = @"GuestP
     [self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey] removeObjectAtIndex:index];
 }
 
-- (NSArray<UTMConfigurationPortForward *> *)portForwards {
+- (NSArray<UTMQemuConfigurationPortForward *> *)portForwards {
     return self.rootDict[kUTMConfigNetworkingKey][kUTMConfigNetworkPortForwardKey];
 }
 

+ 2 - 2
Configuration/UTMConfiguration+Sharing.h → Configuration/UTMQemuConfiguration+Sharing.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (Sharing)
+@interface UTMQemuConfiguration (Sharing)
 
 @property (nonatomic, assign) BOOL shareClipboardEnabled;
 @property (nonatomic, assign) BOOL shareDirectoryEnabled;

+ 4 - 4
Configuration/UTMConfiguration+Sharing.m → Configuration/UTMQemuConfiguration+Sharing.m

@@ -14,8 +14,8 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Sharing.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration+Sharing.h"
+#import "UTMQemuConfiguration+System.h"
 #import "UTM-Swift.h"
 
 extern const NSString *const kUTMConfigSharingKey;
@@ -28,13 +28,13 @@ const NSString *const kUTMConfigDirectoryBookmarkKey = @"DirectoryBookmark";
 const NSString *const kUTMConfigUsb3SupportKey = @"Usb3Support";
 const NSString *const kUTMConfigUsbRedirectMaxKey = @"UsbRedirectMax";
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 @property (nonatomic, readonly) NSMutableDictionary *rootDict;
 
 @end
 
-@implementation UTMConfiguration (Sharing)
+@implementation UTMQemuConfiguration (Sharing)
 
 #pragma mark - Migration
 

+ 2 - 2
Configuration/UTMConfiguration+System.h → Configuration/UTMQemuConfiguration+System.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfiguration (System)
+@interface UTMQemuConfiguration (System)
 
 @property (nonatomic, nullable, copy) NSString *systemArchitecture;
 @property (nonatomic, nullable, copy) NSString *systemCPU;

+ 11 - 11
Configuration/UTMConfiguration+System.m → Configuration/UTMQemuConfiguration+System.m

@@ -14,9 +14,9 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Defaults.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Defaults.h"
+#import "UTMQemuConfiguration+System.h"
 #import "UTM-Swift.h"
 
 extern const NSString *const kUTMConfigSystemKey;
@@ -34,13 +34,13 @@ static const NSString *const kUTMConfigAddArgsKey = @"AddArgs";
 static const NSString *const kUTMConfigSystemUUIDKey = @"SystemUUID";
 static const NSString *const kUTMConfigMachinePropertiesKey = @"MachineProperties";
 
-@interface UTMConfiguration ()
+@interface UTMQemuConfiguration ()
 
 @property (nonatomic, readonly) NSMutableDictionary *rootDict;
 
 @end
 
-@implementation UTMConfiguration (System)
+@implementation UTMQemuConfiguration (System)
 
 #pragma mark - Migration
 
@@ -54,22 +54,22 @@ static const NSString *const kUTMConfigMachinePropertiesKey = @"MachinePropertie
     }
     // Migrate default target
     if ([self.rootDict[kUTMConfigSystemKey][kUTMConfigTargetKey] length] == 0) {
-        NSInteger index = [UTMConfiguration defaultTargetIndexForArchitecture:self.systemArchitecture];
-        self.rootDict[kUTMConfigSystemKey][kUTMConfigTargetKey] = [UTMConfiguration supportedTargetsForArchitecture:self.systemArchitecture][index];
+        NSInteger index = [UTMQemuConfiguration defaultTargetIndexForArchitecture:self.systemArchitecture];
+        self.rootDict[kUTMConfigSystemKey][kUTMConfigTargetKey] = [UTMQemuConfiguration supportedTargetsForArchitecture:self.systemArchitecture][index];
     }
     // Fix issue with boot order
-    NSArray<NSString *> *bootPretty = [UTMConfiguration supportedBootDevicesPretty];
+    NSArray<NSString *> *bootPretty = [UTMQemuConfiguration supportedBootDevicesPretty];
     if ([bootPretty containsObject:self.systemBootDevice]) {
         NSInteger index = [bootPretty indexOfObject:self.systemBootDevice];
-        self.systemBootDevice = [UTMConfiguration supportedBootDevices][index];
+        self.systemBootDevice = [UTMQemuConfiguration supportedBootDevices][index];
     }
     // Default CPU
     if ([self.rootDict[kUTMConfigSystemKey][kUTMConfigCPUKey] length] == 0) {
-        self.rootDict[kUTMConfigSystemKey][kUTMConfigCPUKey] = [UTMConfiguration defaultCPUForTarget:self.systemTarget architecture:self.systemArchitecture];
+        self.rootDict[kUTMConfigSystemKey][kUTMConfigCPUKey] = [UTMQemuConfiguration defaultCPUForTarget:self.systemTarget architecture:self.systemArchitecture];
     }
     // Older versions hard codes properties
     if ([self.version integerValue] < 2) {
-        NSString *machineProp = [UTMConfiguration defaultMachinePropertiesForTarget:self.systemTarget];
+        NSString *machineProp = [UTMQemuConfiguration defaultMachinePropertiesForTarget:self.systemTarget];
         if (machineProp && self.systemMachineProperties.length == 0) {
             self.systemMachineProperties = machineProp;
         }

+ 34 - 0
Configuration/UTMQemuConfiguration.h

@@ -0,0 +1,34 @@
+//
+// Copyright © 2019 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+#import "UTMConfiguration.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UTMQemuConfiguration : UTMConfiguration
+
+- (void)migrateConfigurationIfNecessary;
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path NS_DESIGNATED_INITIALIZER;
+
+- (NSURL*)terminalInputOutputURL;
+- (void)resetDefaults;
+- (void)reloadConfigurationWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 151 - 0
Configuration/UTMQemuConfiguration.m

@@ -0,0 +1,151 @@
+//
+// Copyright © 2019 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Defaults.h"
+#import "UTMQemuConfiguration+Display.h"
+#import "UTMQemuConfiguration+Drives.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration+Networking.h"
+#import "UTMQemuConfiguration+Sharing.h"
+#import "UTMQemuConfiguration+System.h"
+#import "UTM-Swift.h"
+
+const NSString *const kUTMConfigSystemKey = @"System";
+const NSString *const kUTMConfigDisplayKey = @"Display";
+const NSString *const kUTMConfigInputKey = @"Input";
+const NSString *const kUTMConfigNetworkingKey = @"Networking";
+const NSString *const kUTMConfigPrintingKey = @"Printing";
+const NSString *const kUTMConfigSoundKey = @"Sound";
+const NSString *const kUTMConfigSharingKey = @"Sharing";
+const NSString *const kUTMConfigDrivesKey = @"Drives";
+const NSString *const kUTMConfigDebugKey = @"Debug";
+const NSString *const kUTMConfigInfoKey = @"Info";
+const NSString *const kUTMConfigVersionKey = @"ConfigurationVersion";
+
+const NSInteger kCurrentConfigurationVersion = 2;
+
+@interface UTMQemuConfiguration ()
+
+@property (nonatomic, readonly) NSMutableDictionary *rootDict;
+
+@end
+
+@implementation UTMQemuConfiguration {
+    NSMutableDictionary *_rootDict;
+}
+
+@synthesize rootDict = _rootDict;
+
+#pragma mark - Migration
+
+- (void)migrateConfigurationIfNecessary {
+    [self migrateMiscellaneousConfigurationIfNecessary];
+    [self migrateDriveConfigurationIfNecessary];
+    [self migrateNetworkConfigurationIfNecessary];
+    [self migrateSystemConfigurationIfNecessary];
+    [self migrateDisplayConfigurationIfNecessary];
+    [self migrateSharingConfigurationIfNecessary];
+    self.version = @(kCurrentConfigurationVersion);
+}
+
+#pragma mark - Initialization
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        [self resetDefaults];
+    }
+    return self;
+}
+
+- (instancetype)initWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path {
+    self = [super init];
+    if (self) {
+        [self reloadConfigurationWithDictionary:dictionary name:name path:path];
+    }
+    return self;
+}
+
+#pragma mark - Dictionary representation
+
+- (NSDictionary *)dictRepresentation {
+    return (NSDictionary *)_rootDict;
+}
+
+- (NSURL*)terminalInputOutputURL {
+    NSURL* tmpDir = [[NSFileManager defaultManager] temporaryDirectory];
+    NSString* ioFileName = [NSString stringWithFormat: @"%@.terminal", self.name];
+    NSURL* ioFile = [tmpDir URLByAppendingPathComponent: ioFileName];
+    return ioFile;
+}
+
+- (void)resetDefaults {
+    [self propertyWillChange];
+    _rootDict = [@{
+        kUTMConfigSystemKey: [NSMutableDictionary new],
+        kUTMConfigDisplayKey: [NSMutableDictionary new],
+        kUTMConfigInputKey: [NSMutableDictionary new],
+        kUTMConfigNetworkingKey: [NSMutableDictionary new],
+        kUTMConfigPrintingKey: [NSMutableDictionary new],
+        kUTMConfigSoundKey: [NSMutableDictionary new],
+        kUTMConfigSharingKey: [NSMutableDictionary new],
+        kUTMConfigDrivesKey: [NSMutableArray new],
+        kUTMConfigDebugKey: [NSMutableDictionary new],
+        kUTMConfigInfoKey: [NSMutableDictionary new],
+    } mutableCopy];
+    self.version = @(kCurrentConfigurationVersion);
+    [self loadDefaults];
+}
+
+- (void)reloadConfigurationWithDictionary:(NSDictionary *)dictionary name:(NSString *)name path:(NSURL *)path {
+    [self propertyWillChange];
+    _rootDict = CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (__bridge CFDictionaryRef)dictionary, kCFPropertyListMutableContainers));
+    self.name = name;
+    self.existingPath = path;
+    self.selectedCustomIconPath = nil;
+    [self migrateConfigurationIfNecessary];
+}
+
+#pragma mark - NSCopying
+
+- (id)copyWithZone:(NSZone *)zone {
+    return [[UTMQemuConfiguration alloc] initWithDictionary:_rootDict name:self.name path:self.existingPath];
+}
+
+#pragma mark - Settings
+
+- (void)setVersion:(NSNumber *)version {
+    [self propertyWillChange];
+    self.rootDict[kUTMConfigVersionKey] = version;
+}
+
+- (NSNumber *)version {
+    return self.rootDict[kUTMConfigVersionKey];
+}
+
+#pragma mark - Other fields
+
+- (NSString *)subtitle {
+    return self.systemTargetPretty;
+}
+
+- (NSString *)uuid {
+    return self.systemUUID;
+}
+
+@end

+ 3 - 3
Configuration/UTMConfigurationDelegate.h → Configuration/UTMQemuConfigurationDelegate.h

@@ -16,15 +16,15 @@
 
 #import <Foundation/Foundation.h>
 
-@class UTMConfiguration;
+@class UTMQemuConfiguration;
 
 NS_ASSUME_NONNULL_BEGIN
 
-@protocol UTMConfigurationDelegate <NSObject>
+@protocol UTMQemuConfigurationDelegate <NSObject>
 
 @required
 
-@property (nonatomic) UTMConfiguration *configuration;
+@property (nonatomic) UTMQemuConfiguration *configuration;
 
 @end
 

+ 28 - 14
Configuration/UTMConfigurationExtension.swift → Configuration/UTMQemuConfigurationExtension.swift

@@ -16,20 +16,20 @@
 
 import Combine
 
-@objc extension UTMConfiguration: ObservableObject {
+@objc extension UTMQemuConfiguration {
     private static let gibInMib = 1024
     
-    var systemTargetPretty: String {
+    override var systemTargetPretty: String {
         guard let arch = self.systemArchitecture else {
             return ""
         }
         guard let target = self.systemTarget else {
             return ""
         }
-        guard let targets = UTMConfiguration.supportedTargets(forArchitecture: arch) else {
+        guard let targets = UTMQemuConfiguration.supportedTargets(forArchitecture: arch) else {
             return ""
         }
-        guard let prettyTargets = UTMConfiguration.supportedTargets(forArchitecturePretty: arch) else {
+        guard let prettyTargets = UTMQemuConfiguration.supportedTargets(forArchitecturePretty: arch) else {
             return ""
         }
         guard let index = targets.firstIndex(of: target) else {
@@ -38,9 +38,9 @@ import Combine
         return prettyTargets[index]
     }
     
-    var systemArchitecturePretty: String {
-        let archs = UTMConfiguration.supportedArchitectures()
-        let prettyArchs = UTMConfiguration.supportedArchitecturesPretty()
+    override var systemArchitecturePretty: String {
+        let archs = UTMQemuConfiguration.supportedArchitectures()
+        let prettyArchs = UTMQemuConfiguration.supportedArchitecturesPretty()
         guard let arch = self.systemArchitecture else {
             return ""
         }
@@ -50,16 +50,30 @@ import Combine
         return prettyArchs[index]
     }
     
-    var systemMemoryPretty: String {
+    override var systemMemoryPretty: String {
         guard let memory = self.systemMemory else {
-            return NSLocalizedString("Unknown", comment: "UTMConfigurationExtension")
+            return NSLocalizedString("Unknown", comment: "UTMQemuConfigurationExtension")
         }
-        if memory.intValue > UTMConfiguration.gibInMib {
-            return String(format: "%.1f GB", memory.floatValue / Float(UTMConfiguration.gibInMib))
+        if memory.intValue > UTMQemuConfiguration.gibInMib {
+            return String(format: "%.1f GB", memory.floatValue / Float(UTMQemuConfiguration.gibInMib))
         } else {
             return String(format: "%d MB", memory.intValue)
         }
     }
+}
+
+@objc extension UTMConfiguration: ObservableObject {
+    var systemTargetPretty: String {
+        ""
+    }
+    
+    var systemArchitecturePretty: String {
+        ""
+    }
+    
+    var systemMemoryPretty: String {
+        ""
+    }
     
     var existingCustomIconURL: URL? {
         if let current = self.selectedCustomIconPath {
@@ -92,7 +106,7 @@ import Combine
     }
 }
 
-@objc extension UTMConfigurationPortForward: ObservableObject {
+@objc extension UTMQemuConfigurationPortForward: ObservableObject {
     func propertyWillChange() -> Void {
         if #available(iOS 13, macOS 11, *) {
             DispatchQueue.main.async { self.objectWillChange.send() }
@@ -111,7 +125,7 @@ import Combine
 extension UTMDiskImageType: CustomStringConvertible {
     public var description: String {
         let index = rawValue
-        let imageTypeList = UTMConfiguration.supportedImageTypes()
+        let imageTypeList = UTMQemuConfiguration.supportedImageTypes()
         if index >= 0 && index < imageTypeList.count {
             return imageTypeList[index]
         } else {
@@ -120,7 +134,7 @@ extension UTMDiskImageType: CustomStringConvertible {
     }
     
     static public func enumFromString(_ str: String?) -> UTMDiskImageType {
-        let imageTypeList = UTMConfiguration.supportedImageTypes()
+        let imageTypeList = UTMQemuConfiguration.supportedImageTypes()
         guard let unwrapStr = str else {
             return UTMDiskImageType.disk
         }

+ 1 - 1
Configuration/UTMConfigurationPortForward.h → Configuration/UTMQemuConfigurationPortForward.h

@@ -18,7 +18,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMConfigurationPortForward : NSObject
+@interface UTMQemuConfigurationPortForward : NSObject
 
 @property (nonatomic, nullable) NSString *protocol;
 @property (nonatomic) NSString *hostAddress;

+ 2 - 2
Configuration/UTMConfigurationPortForward.m → Configuration/UTMQemuConfigurationPortForward.m

@@ -14,10 +14,10 @@
 // limitations under the License.
 //
 
-#import "UTMConfigurationPortForward.h"
+#import "UTMQemuConfigurationPortForward.h"
 #import "UTM-Swift.h"
 
-@implementation UTMConfigurationPortForward
+@implementation UTMQemuConfigurationPortForward
 
 @synthesize protocol = _protocol;
 @synthesize hostAddress = _hostAddress;

+ 1 - 1
Managers/UTMDrive.h

@@ -15,7 +15,7 @@
 //
 
 #import <Foundation/Foundation.h>
-#import "UTMConfiguration+Drives.h"
+#import "UTMQemuConfiguration+Drives.h"
 
 typedef NS_ENUM(NSInteger, UTMDriveStatus) {
     UTMDriveStatusFixed,

+ 2 - 2
Managers/UTMDrive.m

@@ -15,12 +15,12 @@
 //
 
 #import "UTMDrive.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Constants.h"
 
 @implementation UTMDrive
 
 - (NSString *)label {
-    NSString *imageTypeStr = [UTMConfiguration supportedImageTypesPretty][self.imageType];
+    NSString *imageTypeStr = [UTMQemuConfiguration supportedImageTypesPretty][self.imageType];
     NSString *filename = self.path.lastPathComponent;
     if (!filename) {
         filename = NSLocalizedString(@"none", @"UTMDrive");

+ 1 - 1
Managers/UTMQemu.h

@@ -19,7 +19,7 @@
 
 typedef void * _Nullable (* _Nonnull UTMQemuThreadEntry)(void * _Nullable args);
 
-@class UTMConfiguration;
+@class UTMQemuConfiguration;
 @class UTMLogging;
 
 NS_ASSUME_NONNULL_BEGIN

+ 2 - 2
Managers/UTMQemuSystem.h

@@ -20,14 +20,14 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface UTMQemuSystem : UTMQemu
 
-@property (nonatomic) UTMConfiguration *configuration;
+@property (nonatomic) UTMQemuConfiguration *configuration;
 @property (nonatomic) NSURL *imgPath;
 @property (nonatomic, nullable) NSString *snapshot;
 @property (nonatomic) NSInteger qmpPort;
 @property (nonatomic) NSInteger spicePort;
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithConfiguration:(UTMConfiguration *)configuration imgPath:(NSURL *)imgPath;
+- (instancetype)initWithConfiguration:(UTMQemuConfiguration *)configuration imgPath:(NSURL *)imgPath;
 - (void)updateArgvWithUserOptions:(BOOL)userOptions;
 - (void)startWithCompletion:(void(^)(BOOL, NSString *))completion;
 

+ 12 - 12
Managers/UTMQemuSystem.m

@@ -18,15 +18,15 @@
 #import <sys/sysctl.h>
 #import <TargetConditionals.h>
 #import "UTMQemuSystem.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Display.h"
-#import "UTMConfiguration+Drives.h"
-#import "UTMConfiguration+Miscellaneous.h"
-#import "UTMConfiguration+Networking.h"
-#import "UTMConfiguration+Sharing.h"
-#import "UTMConfiguration+System.h"
-#import "UTMConfigurationPortForward.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Display.h"
+#import "UTMQemuConfiguration+Drives.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration+Networking.h"
+#import "UTMQemuConfiguration+Sharing.h"
+#import "UTMQemuConfiguration+System.h"
+#import "UTMQemuConfigurationPortForward.h"
 #import "UTMJailbreak.h"
 #import "UTMLogging.h"
 
@@ -84,7 +84,7 @@ static size_t sysctl_read(const char *name) {
     return ncpu;
 }
 
-- (instancetype)initWithConfiguration:(UTMConfiguration *)configuration imgPath:(nonnull NSURL *)imgPath {
+- (instancetype)initWithConfiguration:(UTMQemuConfiguration *)configuration imgPath:(nonnull NSURL *)imgPath {
     self = [self init];
     if (self) {
         self.configuration = configuration;
@@ -265,7 +265,7 @@ static size_t sysctl_read(const char *name) {
             if ([path characterAtIndex:0] == '/') {
                 fullPathURL = [NSURL fileURLWithPath:path isDirectory:NO];
             } else {
-                fullPathURL = [[self.imgPath URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory]] URLByAppendingPathComponent:[self.configuration driveImagePathForIndex:i]];
+                fullPathURL = [[self.imgPath URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory]] URLByAppendingPathComponent:[self.configuration driveImagePathForIndex:i]];
             }
             [self accessDataWithBookmark:[fullPathURL bookmarkDataWithOptions:0
                                                includingResourceValuesForKeys:nil
@@ -384,7 +384,7 @@ static size_t sysctl_read(const char *name) {
             [netstr appendFormat:@",domainname=%@", self.configuration.networkDhcpDomain];
         }
         for (NSUInteger i = 0; i < [self.configuration countPortForwards]; i++) {
-            UTMConfigurationPortForward *portForward = [self.configuration portForwardForIndex:i];
+            UTMQemuConfigurationPortForward *portForward = [self.configuration portForwardForIndex:i];
             [netstr appendFormat:@",hostfwd=%@:%@:%@-%@:%@", portForward.protocol, portForward.hostAddress, portForward.hostPort, portForward.guestAddress, portForward.guestPort];
         }
         [self pushArgv:netstr];

+ 2 - 2
Managers/UTMVirtualMachine+Drives.h → Managers/UTMQemuVirtualMachine+Drives.h

@@ -14,13 +14,13 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine.h"
+#import "UTMQemuVirtualMachine.h"
 
 @class UTMDrive;
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMVirtualMachine (Drives)
+@interface UTMQemuVirtualMachine (Drives)
 
 @property (nonatomic, readonly) NSArray<UTMDrive *> *drives;
 

+ 9 - 9
Managers/UTMVirtualMachine+Drives.m → Managers/UTMQemuVirtualMachine+Drives.m

@@ -14,7 +14,7 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine+Drives.h"
+#import "UTMQemuVirtualMachine+Drives.h"
 #import "UTMLogging.h"
 #import "UTMViewState.h"
 #import "UTMDrive.h"
@@ -23,7 +23,7 @@
 
 extern NSString *const kUTMErrorDomain;
 
-@interface UTMVirtualMachine ()
+@interface UTMQemuVirtualMachine ()
 
 @property (nonatomic, readonly, nullable) UTMQemuManager *qemu;
 @property (nonatomic, readonly, nullable) UTMQemu *system;
@@ -32,18 +32,18 @@ extern NSString *const kUTMErrorDomain;
 
 @end
 
-@implementation UTMVirtualMachine (Drives)
+@implementation UTMQemuVirtualMachine (Drives)
 
 - (NSArray<UTMDrive *> *)drives {
-    NSInteger count = self.configuration.countDrives;
+    NSInteger count = self.qemuConfig.countDrives;
     id drives = [NSMutableArray<UTMDrive *> arrayWithCapacity:count];
     for (NSInteger i = 0; i < count; i++) {
         UTMDrive *drive = [UTMDrive new];
         drive.index = i;
-        drive.imageType = [self.configuration driveImageTypeForIndex:i];
-        drive.interface = [self.configuration driveInterfaceTypeForIndex:i];
-        drive.name = [self.configuration driveNameForIndex:i];
-        if ([self.configuration driveRemovableForIndex:i]) {
+        drive.imageType = [self.qemuConfig driveImageTypeForIndex:i];
+        drive.interface = [self.qemuConfig driveInterfaceTypeForIndex:i];
+        drive.name = [self.qemuConfig driveNameForIndex:i];
+        if ([self.qemuConfig driveRemovableForIndex:i]) {
             // removable drive -> path stored only in viewState
             NSString *path = [self.viewState pathForRemovableDrive:drive.name];
             if (path.length > 0) {
@@ -56,7 +56,7 @@ extern NSString *const kUTMErrorDomain;
         } else {
             // fixed drive -> path stored in configuration
             drive.status = UTMDriveStatusFixed;
-            drive.path = [self.configuration driveImagePathForIndex:i];
+            drive.path = [self.qemuConfig driveImagePathForIndex:i];
         }
         [drives addObject:drive];
     }

+ 2 - 2
Managers/UTMVirtualMachine+SPICE.h → Managers/UTMQemuVirtualMachine+SPICE.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine.h"
+#import "UTMQemuVirtualMachine.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMVirtualMachine (SPICE)
+@interface UTMQemuVirtualMachine (SPICE)
 
 @property (nonatomic, readonly) BOOL hasShareDirectoryEnabled;
 @property (nonatomic, readonly) BOOL hasUsbRedirection;

+ 10 - 10
Managers/UTMVirtualMachine+SPICE.m → Managers/UTMQemuVirtualMachine+SPICE.m

@@ -15,10 +15,10 @@
 //
 
 #import <TargetConditionals.h>
-#import "UTMVirtualMachine+SPICE.h"
+#import "UTMQemuVirtualMachine+SPICE.h"
 #import "CocoaSpice.h"
-#import "UTMConfiguration+Display.h"
-#import "UTMConfiguration+Sharing.h"
+#import "UTMQemuConfiguration+Display.h"
+#import "UTMQemuConfiguration+Sharing.h"
 #import "UTMLogging.h"
 #import "UTMQemuManager.h"
 #import "UTMSpiceIO.h"
@@ -35,7 +35,7 @@ static const NSURLBookmarkCreationOptions kBookmarkCreationOptions = NSURLBookma
 static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBookmarkResolutionWithSecurityScope;
 #endif
 
-@interface UTMVirtualMachine ()
+@interface UTMQemuVirtualMachine ()
 
 @property (nonatomic, readonly, nullable) UTMQemuManager *qemu;
 @property (nonatomic, readonly, nullable) id<UTMInputOutput> ioService;
@@ -44,7 +44,7 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
 
 @end
 
-@implementation UTMVirtualMachine (SPICE)
+@implementation UTMQemuVirtualMachine (SPICE)
 
 - (UTMSpiceIO *)spiceIoWithError:(NSError * _Nullable __autoreleasing *)error {
     if (![self.ioService isKindOfClass:[UTMSpiceIO class]]) {
@@ -59,7 +59,7 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
 #pragma mark - Shared Directory
 
 - (BOOL)hasShareDirectoryEnabled {
-    return self.configuration.shareDirectoryEnabled && !self.configuration.displayConsoleOnly;
+    return self.qemuConfig.shareDirectoryEnabled && !self.qemuConfig.displayConsoleOnly;
 }
 
 - (BOOL)saveSharedDirectory:(NSURL *)url error:(NSError * _Nullable __autoreleasing *)error {
@@ -105,7 +105,7 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
         }
         return NO;
     }
-    if (!self.configuration.shareDirectoryEnabled) {
+    if (!self.qemuConfig.shareDirectoryEnabled) {
         return YES;
     }
     UTMSpiceIO *spiceIO = [self spiceIoWithError:error];
@@ -118,9 +118,9 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
     if (self.viewState.sharedDirectory) {
         UTMLog(@"found shared directory bookmark");
         bookmark = self.viewState.sharedDirectory;
-    } else if (self.configuration.shareDirectoryBookmark) {
+    } else if (self.qemuConfig.shareDirectoryBookmark) {
         UTMLog(@"found shared directory bookmark (legacy)");
-        bookmark = self.configuration.shareDirectoryBookmark;
+        bookmark = self.qemuConfig.shareDirectoryBookmark;
         legacy = YES;
     }
     if (bookmark) {
@@ -177,7 +177,7 @@ static const NSURLBookmarkResolutionOptions kBookmarkResolutionOptions = NSURLBo
 #pragma mark - USB redirection
 
 - (BOOL)hasUsbRedirection {
-    return jb_has_usb_entitlement() && !self.configuration.displayConsoleOnly;
+    return jb_has_usb_entitlement() && !self.qemuConfig.displayConsoleOnly;
 }
 
 @end

+ 2 - 2
Managers/UTMVirtualMachine+Terminal.h → Managers/UTMQemuVirtualMachine+Terminal.h

@@ -14,11 +14,11 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine.h"
+#import "UTMQemuVirtualMachine.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMVirtualMachine (Terminal)
+@interface UTMQemuVirtualMachine (Terminal)
 
 - (void)sendInput:(NSString*)inputStr;
 

+ 3 - 3
Managers/UTMVirtualMachine+Terminal.m → Managers/UTMQemuVirtualMachine+Terminal.m

@@ -14,16 +14,16 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine+Terminal.h"
+#import "UTMQemuVirtualMachine+Terminal.h"
 #import "UTMTerminalIO.h"
 
-@interface UTMVirtualMachine ()
+@interface UTMQemuVirtualMachine ()
 
 @property (nonatomic, readonly, nullable) id<UTMInputOutput> ioService;
 
 @end
 
-@implementation UTMVirtualMachine (Terminal)
+@implementation UTMQemuVirtualMachine (Terminal)
 
 - (void)sendInput:(NSString *)inputStr {
     NSAssert([self.ioService isKindOfClass:[UTMTerminalIO class]], @"Invalid ioService type '%@' should be UTMTerminalIO", NSStringFromClass([self.ioService class]));

+ 38 - 0
Managers/UTMQemuVirtualMachine.h

@@ -0,0 +1,38 @@
+//
+// Copyright © 2021 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMVirtualMachine.h"
+#import "UTMQemuManagerDelegate.h"
+
+@class UTMQemuConfiguration;
+
+typedef NS_ENUM(NSInteger, UTMDisplayType) {
+    UTMDisplayTypeFullGraphic,
+    UTMDisplayTypeConsole
+};
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UTMQemuVirtualMachine : UTMVirtualMachine<UTMQemuManagerDelegate>
+
+@property (nonatomic, weak, nullable) id ioDelegate;
+@property (nonatomic, readonly) UTMQemuConfiguration *qemuConfig;
+
+- (UTMDisplayType)supportedDisplayType;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 506 - 0
Managers/UTMQemuVirtualMachine.m

@@ -0,0 +1,506 @@
+//
+// Copyright © 2021 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <TargetConditionals.h>
+#import "UTMVirtualMachine-Private.h"
+#import "UTMQemuVirtualMachine.h"
+#import "UTMQemuVirtualMachine+Drives.h"
+#import "UTMQemuVirtualMachine+SPICE.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Display.h"
+#import "UTMQemuConfiguration+Drives.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
+#import "UTMViewState.h"
+#import "UTMQemuManager.h"
+#import "UTMQemuSystem.h"
+#import "UTMTerminalIO.h"
+#import "UTMSpiceIO.h"
+#import "UTMLogging.h"
+#import "UTMScreenshot.h"
+#import "UTMPortAllocator.h"
+#import "qapi-events.h"
+
+const int kQMPMaxConnectionTries = 30; // qemu needs to start spice server first
+const int64_t kStopTimeout = (int64_t)30*NSEC_PER_SEC;
+
+extern NSString *const kUTMBundleConfigFilename;
+NSString *const kSuspendSnapshotName = @"suspend";
+
+
+@interface UTMQemuVirtualMachine ()
+
+@property (nonatomic, readonly) UTMQemuManager *qemu;
+@property (nonatomic, readwrite, nullable) UTMQemuSystem *system;
+@property (nonatomic, readonly, nullable) id<UTMInputOutput> ioService;
+
+@end
+
+@implementation UTMQemuVirtualMachine {
+    dispatch_semaphore_t _will_quit_sema;
+    dispatch_semaphore_t _qemu_exit_sema;
+}
+
+- (UTMQemuConfiguration *)qemuConfig {
+    return (UTMQemuConfiguration *)self.config;
+}
+
+- (id)ioDelegate {
+    if ([self.ioService isKindOfClass:[UTMSpiceIO class]]) {
+        return ((UTMSpiceIO *)self.ioService).delegate;
+    } else if ([self.ioService isKindOfClass:[UTMTerminalIO class]]) {
+        return ((UTMTerminalIO *)self.ioService).terminal.delegate;
+    } else {
+        return nil;
+    }
+}
+
+- (void)setIoDelegate:(id)ioDelegate {
+    if ([self.ioService isKindOfClass:[UTMSpiceIO class]]) {
+        ((UTMSpiceIO *)self.ioService).delegate = ioDelegate;
+    } else if ([self.ioService isKindOfClass:[UTMTerminalIO class]]) {
+        ((UTMTerminalIO *)self.ioService).terminal.delegate = ioDelegate;
+    } else if (self.state == kVMStarted) {
+        NSAssert(0, @"ioService class is invalid: %@", NSStringFromClass([self.ioService class]));
+    }
+}
+
+- (instancetype)init {
+    self = [super init];
+    if (self) {
+        _will_quit_sema = dispatch_semaphore_create(0);
+        _qemu_exit_sema = dispatch_semaphore_create(0);
+    }
+    return self;
+}
+
+#pragma mark - Configuration
+
+- (BOOL)loadConfigurationWithReload:(BOOL)reload error:(NSError * _Nullable __autoreleasing *)err {
+    NSAssert(self.path != nil, @"Cannot load configuration on an unsaved VM.");
+    NSString *name = [UTMVirtualMachine virtualMachineName:self.path];
+    NSDictionary *plist = [self loadPlist:[self.path URLByAppendingPathComponent:kUTMBundleConfigFilename] withError:err];
+    if (!plist) {
+        UTMLog(@"Failed to parse config for %@, error: %@", self.path, err ? *err : nil);
+        return NO;
+    }
+    if (reload) {
+        NSAssert(self.qemuConfig != nil, @"Trying to reload when no configuration is loaded.");
+        [self.qemuConfig reloadConfigurationWithDictionary:plist name:name path:self.path];
+    } else {
+        self.config = [[UTMQemuConfiguration alloc] initWithDictionary:plist name:name path:self.path];
+    }
+    return [super loadConfigurationWithReload:reload error:err];
+}
+
+- (BOOL)saveConfigurationWithError:(NSError * _Nullable __autoreleasing *)err {
+    NSURL *url = [self packageURLForName:self.config.name];
+    if (![self savePlist:[url URLByAppendingPathComponent:kUTMBundleConfigFilename]
+                    dict:self.qemuConfig.dictRepresentation
+               withError:err]) {
+        return NO;
+    }
+    return [super saveConfigurationWithError:err];
+}
+
+#pragma mark - VM actions
+
+- (BOOL)startVM {
+    @synchronized (self) {
+        if (self.busy || (self.state != kVMStopped && self.state != kVMSuspended)) {
+            return NO; // already started
+        } else {
+            self.busy = YES;
+        }
+    }
+    // start logging
+    if (self.qemuConfig.debugLogEnabled) {
+        [self.logging logToFile:[self.path URLByAppendingPathComponent:[UTMQemuConfiguration debugLogName]]];
+    }
+    
+    if (!self.system) {
+        self.system = [[UTMQemuSystem alloc] initWithConfiguration:self.qemuConfig imgPath:self.path];
+        self.system.logging = self.logging;
+#if !TARGET_OS_IPHONE
+        [self.system setupXpc];
+#endif
+        self.system.qmpPort = [[UTMPortAllocator sharedInstance] allocatePort];
+        self.system.spicePort = [[UTMPortAllocator sharedInstance] allocatePort];
+        _qemu = [[UTMQemuManager alloc] initWithPort:self.system.qmpPort];
+        _qemu.delegate = self;
+    }
+
+    if (!self.system) {
+        [self errorTriggered:NSLocalizedString(@"Internal error starting VM.", @"UTMVirtualMachine")];
+        self.busy = NO;
+        return NO;
+    }
+    
+    if (!_ioService) {
+        _ioService = [self inputOutputServiceWithPort:self.system.spicePort];
+    }
+    
+    self.delegate.vmMessage = nil;
+    [self changeState:kVMStarting];
+    if (self.qemuConfig.debugLogEnabled) {
+        [_ioService setDebugMode:YES];
+    }
+    
+    BOOL ioStatus = [_ioService startWithError: nil];
+    if (!ioStatus) {
+        [self errorTriggered:NSLocalizedString(@"Internal error starting main loop.", @"UTMVirtualMachine")];
+        self.busy = NO;
+        return NO;
+    }
+    if (self.viewState.suspended) {
+        self.system.snapshot = kSuspendSnapshotName;
+    }
+    [self.system startWithCompletion:^(BOOL success, NSString *msg){
+        if (!success) {
+            [self errorTriggered:msg];
+        }
+        dispatch_semaphore_signal(self->_qemu_exit_sema);
+    }];
+    [self->_ioService connectWithCompletion:^(BOOL success, NSString * _Nullable msg) {
+        if (!success) {
+            [self errorTriggered:msg];
+        } else {
+            [self changeState:kVMStarted];
+            [self restoreViewState];
+            if (self.viewState.suspended) {
+                [self deleteSaveVM];
+            }
+        }
+    }];
+    self->_qemu.retries = kQMPMaxConnectionTries;
+    [self->_qemu connect];
+    self.busy = NO;
+    return YES;
+}
+
+- (BOOL)quitVMForce:(BOOL)force {
+    @synchronized (self) {
+        if (!force && (self.busy || self.state != kVMStarted)) {
+            return NO; // already stopping
+        } else {
+            self.busy = YES;
+        }
+    }
+    self.viewState.suspended = NO;
+    [self syncViewState];
+    if (!force) {
+        [self changeState:kVMStopping];
+    }
+    // save view settings early to win exit race
+    [self saveViewState];
+    
+    _qemu.retries = 0;
+    [_qemu vmQuitWithCompletion:nil];
+    if (force || dispatch_semaphore_wait(_will_quit_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Stop operation timeout or force quit");
+    }
+    [_qemu disconnect];
+    _qemu.delegate = nil;
+    _qemu = nil;
+    [_ioService disconnect];
+    _ioService = nil;
+    
+    if (force || dispatch_semaphore_wait(_qemu_exit_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Exit operation timeout or force quit");
+    }
+    [self.system stopQemu];
+    if (self.system.qmpPort) {
+        [[UTMPortAllocator sharedInstance] freePort:self.system.qmpPort];
+        self.system.qmpPort = 0;
+    }
+    if (self.system.spicePort) {
+        [[UTMPortAllocator sharedInstance] freePort:self.system.spicePort];
+        self.system.spicePort = 0;
+    }
+    self.system = nil;
+    [self changeState:kVMStopped];
+    // stop logging
+    [self.logging endLog];
+    self.busy = NO;
+    return YES;
+}
+
+- (BOOL)resetVM {
+    @synchronized (self) {
+        if (self.busy || (self.state != kVMStarted && self.state != kVMPaused)) {
+            return NO; // already stopping
+        } else {
+            self.busy = YES;
+        }
+    }
+    [self syncViewState];
+    [self changeState:kVMStopping];
+    if (self.viewState.suspended) {
+        [self deleteSaveVM];
+    }
+    [self saveViewState];
+    __block BOOL success = YES;
+    dispatch_semaphore_t reset_sema = dispatch_semaphore_create(0);
+    [_qemu vmResetWithCompletion:^(NSError *err) {
+        UTMLog(@"reset callback: err? %@", err);
+        if (err) {
+            UTMLog(@"error: %@", err);
+            success = NO;
+        }
+        dispatch_semaphore_signal(reset_sema);
+    }];
+    if (dispatch_semaphore_wait(reset_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Reset operation timeout");
+        success = NO;
+    }
+    if (success) {
+        [self changeState:kVMStarted];
+    } else {
+        [self changeState:kVMError];
+    }
+    self.busy = NO;
+    return success;
+}
+
+- (BOOL)pauseVM {
+    @synchronized (self) {
+        if (self.busy || self.state != kVMStarted) {
+            return NO; // already stopping
+        } else {
+            self.busy = YES;
+        }
+    }
+    [self syncViewState];
+    [self changeState:kVMPausing];
+    [self saveScreenshot];
+    __block BOOL success = YES;
+    dispatch_semaphore_t suspend_sema = dispatch_semaphore_create(0);
+    [_qemu vmStopWithCompletion:^(NSError * err) {
+        UTMLog(@"stop callback: err? %@", err);
+        if (err) {
+            UTMLog(@"error: %@", err);
+            success = NO;
+        }
+        dispatch_semaphore_signal(suspend_sema);
+    }];
+    if (dispatch_semaphore_wait(suspend_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Stop operation timeout");
+        success = NO;
+    }
+    if (success) {
+        [self changeState:kVMPaused];
+    } else {
+        [self changeState:kVMError];
+    }
+    self.busy = NO;
+    return success;
+}
+
+- (BOOL)saveVM {
+    @synchronized (self) {
+        if (self.busy || (self.state != kVMPaused && self.state != kVMStarted)) {
+            return NO;
+        } else {
+            self.busy = YES;
+        }
+    }
+    UTMVMState state = self.state;
+    [self changeState:kVMPausing];
+    __block BOOL success = YES;
+    dispatch_semaphore_t save_sema = dispatch_semaphore_create(0);
+    [_qemu vmSaveWithCompletion:^(NSString *result, NSError *err) {
+        UTMLog(@"save callback: %@", result);
+        if (err) {
+            UTMLog(@"error: %@", err);
+            success = NO;
+        } else if ([result localizedCaseInsensitiveContainsString:@"Error"]) {
+            UTMLog(@"save result: %@", result);
+            success = NO; // error message
+        }
+        dispatch_semaphore_signal(save_sema);
+    } snapshotName:kSuspendSnapshotName];
+    if (dispatch_semaphore_wait(save_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Save operation timeout");
+        success = NO;
+    } else if (success) {
+        UTMLog(@"Save completed");
+        self.viewState.suspended = YES;
+        [self saveViewState];
+        [self saveScreenshot];
+    }
+    [self changeState:state];
+    self.busy = NO;
+    return success;
+}
+
+- (BOOL)deleteSaveVM {
+    __block BOOL success = YES;
+    if (self.qemu) { // if QEMU is running
+        dispatch_semaphore_t save_sema = dispatch_semaphore_create(0);
+        [_qemu vmDeleteSaveWithCompletion:^(NSString *result, NSError *err) {
+            UTMLog(@"delete save callback: %@", result);
+            if (err) {
+                UTMLog(@"error: %@", err);
+                success = NO;
+            } else if ([result localizedCaseInsensitiveContainsString:@"Error"]) {
+                UTMLog(@"save result: %@", result);
+                success = NO; // error message
+            }
+            dispatch_semaphore_signal(save_sema);
+        } snapshotName:kSuspendSnapshotName];
+        if (dispatch_semaphore_wait(save_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+            UTMLog(@"Delete save operation timeout");
+            success = NO;
+        } else {
+            UTMLog(@"Delete save completed");
+        }
+    } // otherwise we mark as deleted
+    self.viewState.suspended = NO;
+    [self saveViewState];
+    return success;
+}
+
+- (BOOL)resumeVM {
+    @synchronized (self) {
+        if (self.busy || self.state != kVMPaused) {
+            return NO;
+        } else {
+            self.busy = YES;
+        }
+    }
+    [self changeState:kVMResuming];
+    __block BOOL success = YES;
+    dispatch_semaphore_t resume_sema = dispatch_semaphore_create(0);
+    [_qemu vmResumeWithCompletion:^(NSError *err) {
+        UTMLog(@"resume callback: err? %@", err);
+        if (err) {
+            UTMLog(@"error: %@", err);
+            success = NO;
+        }
+        dispatch_semaphore_signal(resume_sema);
+    }];
+    if (dispatch_semaphore_wait(resume_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
+        UTMLog(@"Resume operation timeout");
+        success = NO;
+    }
+    if (success) {
+        [self changeState:kVMStarted];
+        [self restoreViewState];
+    } else {
+        [self changeState:kVMError];
+    }
+    if (self.viewState.suspended) {
+        [self deleteSaveVM];
+    }
+    self.busy = NO;
+    return success;
+}
+
+- (UTMDisplayType)supportedDisplayType {
+    if ([self.qemuConfig displayConsoleOnly]) {
+        return UTMDisplayTypeConsole;
+    } else {
+        return UTMDisplayTypeFullGraphic;
+    }
+}
+
+- (id<UTMInputOutput>)inputOutputServiceWithPort:(NSInteger)port {
+    if ([self supportedDisplayType] == UTMDisplayTypeConsole) {
+        return [[UTMTerminalIO alloc] initWithConfiguration:[self.qemuConfig copy]];
+    } else {
+        return [[UTMSpiceIO alloc] initWithConfiguration:[self.qemuConfig copy] port:port];
+    }
+}
+
+#pragma mark - Qemu manager delegate
+
+- (void)qemuHasWakeup:(UTMQemuManager *)manager {
+    UTMLog(@"qemuHasWakeup");
+}
+
+- (void)qemuHasResumed:(UTMQemuManager *)manager {
+    UTMLog(@"qemuHasResumed");
+}
+
+- (void)qemuHasStopped:(UTMQemuManager *)manager {
+    UTMLog(@"qemuHasStopped");
+}
+
+- (void)qemuHasReset:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason {
+    UTMLog(@"qemuHasReset, reason = %s", ShutdownCause_str(reason));
+}
+
+- (void)qemuHasSuspended:(UTMQemuManager *)manager {
+    UTMLog(@"qemuHasSuspended");
+}
+
+- (void)qemuWillQuit:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason {
+    UTMLog(@"qemuWillQuit, reason = %s", ShutdownCause_str(reason));
+    dispatch_semaphore_signal(_will_quit_sema);
+    if (!self.busy) {
+        [self quitVM];
+    }
+}
+
+- (void)qemuError:(UTMQemuManager *)manager error:(NSString *)error {
+    UTMLog(@"qemuError: %@", error);
+    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
+        [self errorTriggered:error];
+    });
+}
+
+// this is called right before we execute qmp_cont so we can setup additional option
+- (void)qemuQmpDidConnect:(UTMQemuManager *)manager {
+    UTMLog(@"qemuQmpDidConnect");
+    __autoreleasing NSError *err = nil;
+    NSString *errMsg = nil;
+    if (!self.qemuConfig.displayConsoleOnly) {
+        if (![self startSharedDirectoryWithError:&err]) {
+            errMsg = [NSString stringWithFormat:NSLocalizedString(@"Error trying to start shared directory: %@", @"UTMVirtualMachine"), err.localizedDescription];
+            UTMLog(@"%@", errMsg);
+        }
+    }
+    if (!err && ![self restoreRemovableDrivesFromBookmarksWithError:&err]) {
+        errMsg = [NSString stringWithFormat:NSLocalizedString(@"Error trying to restore removable drives: %@", @"UTMVirtualMachine"), err.localizedDescription];
+        UTMLog(@"%@", errMsg);
+    }
+    if (errMsg) {
+        dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
+            [self errorTriggered:errMsg];
+        });
+    }
+}
+
+#pragma mark - View State
+
+- (void)syncViewState {
+    [self.ioService syncViewState:self.viewState];
+    [super syncViewState];
+}
+
+- (void)restoreViewState {
+    [self.ioService restoreViewState:self.viewState];
+    [super restoreViewState];
+}
+
+#pragma mark - Screenshot
+
+- (void)saveScreenshot {
+    self.screenshot = [self.ioService screenshot];
+    [super saveScreenshot];
+}
+
+@end

+ 3 - 3
Managers/UTMSpiceIO.h

@@ -19,7 +19,7 @@
 #import "CSConnectionDelegate.h"
 #import "UTMSpiceIODelegate.h"
 
-@class UTMConfiguration;
+@class UTMQemuConfiguration;
 @class CSDisplayMetal;
 @class CSInput;
 @class CSUSBManager;
@@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface UTMSpiceIO : NSObject<UTMInputOutput, CSConnectionDelegate>
 
-@property (nonatomic, readonly, nonnull) UTMConfiguration* configuration;
+@property (nonatomic, readonly, nonnull) UTMQemuConfiguration* configuration;
 @property (nonatomic, readonly, nullable) CSDisplayMetal *primaryDisplay;
 @property (nonatomic, readonly, nullable) CSInput *primaryInput;
 #if !defined(WITH_QEMU_TCI)
@@ -37,7 +37,7 @@ NS_ASSUME_NONNULL_BEGIN
 @property (nonatomic, weak, nullable) id<UTMSpiceIODelegate> delegate;
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithConfiguration: (UTMConfiguration*) configuration port:(NSInteger)port NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithConfiguration: (UTMQemuConfiguration*) configuration port:(NSInteger)port NS_DESIGNATED_INITIALIZER;
 - (void)changeSharedDirectory:(NSURL *)url;
 
 @end

+ 4 - 4
Managers/UTMSpiceIO.m

@@ -15,9 +15,9 @@
 //
 
 #import "UTMSpiceIO.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Miscellaneous.h"
-#import "UTMConfiguration+Sharing.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration+Sharing.h"
 #import "UTMLogging.h"
 #import "UTMViewState.h"
 #import "CocoaSpice.h"
@@ -49,7 +49,7 @@ typedef void (^connectionCallback_t)(BOOL success, NSString * _Nullable msg);
 
 @implementation UTMSpiceIO
 
-- (instancetype)initWithConfiguration:(UTMConfiguration *)configuration port:(NSInteger)port {
+- (instancetype)initWithConfiguration:(UTMQemuConfiguration *)configuration port:(NSInteger)port {
     if (self = [super init]) {
         _configuration = configuration;
         _port = port;

+ 2 - 2
Managers/UTMTerminalIO.h

@@ -18,7 +18,7 @@
 #import "UTMTerminal.h"
 #import "UTMInputOutput.h"
 
-@class UTMConfiguration;
+@class UTMQemuConfiguration;
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN
 
 @property (nonatomic, strong, readonly) UTMTerminal* terminal;
 
-- (id)initWithConfiguration: (UTMConfiguration*) configuration;
+- (id)initWithConfiguration: (UTMQemuConfiguration*) configuration;
 
 @end
 

+ 2 - 2
Managers/UTMTerminalIO.m

@@ -16,11 +16,11 @@
 
 #import "UTMLogging.h"
 #import "UTMTerminalIO.h"
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 @implementation UTMTerminalIO
 
-- (id)initWithConfiguration: (UTMConfiguration*) configuration {
+- (id)initWithConfiguration: (UTMQemuConfiguration*) configuration {
     if (self = [super init]) {
         NSURL* terminalURL = [configuration terminalInputOutputURL];
         _terminal = [[UTMTerminal alloc] initWithURL: terminalURL];

+ 38 - 0
Managers/UTMVirtualMachine+IO.h

@@ -0,0 +1,38 @@
+//
+// Copyright © 2021 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMVirtualMachine.h"
+
+@class UTMDrive;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UTMVirtualMachine (IO)
+
+@property (nonatomic, readonly) NSArray<UTMDrive *> *drives;
+@property (nonatomic, readonly) BOOL hasShareDirectoryEnabled;
+@property (nonatomic, readonly) BOOL hasUsbRedirection;
+
+- (BOOL)ejectDrive:(UTMDrive *)drive force:(BOOL)force error:(NSError * _Nullable *)error;
+- (BOOL)changeMediumForDrive:(UTMDrive *)drive url:(NSURL *)url error:(NSError * _Nullable *)error;
+- (BOOL)restoreRemovableDrivesFromBookmarksWithError:(NSError * _Nullable __autoreleasing *)error;
+
+- (BOOL)changeSharedDirectory:(NSURL *)url error:(NSError * _Nullable *)error;
+- (void)clearSharedDirectory;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 21 - 0
Managers/UTMVirtualMachine+IO.m

@@ -0,0 +1,21 @@
+//
+// Copyright © 2021 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMVirtualMachine+IO.h"
+
+@implementation UTMVirtualMachine (IO)
+
+@end

+ 54 - 0
Managers/UTMVirtualMachine-Private.h

@@ -0,0 +1,54 @@
+//
+// Copyright © 2021 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "UTMVirtualMachine.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface UTMVirtualMachine ()
+
+@property (nonatomic, readwrite, nullable) NSURL *path;
+@property (nonatomic, strong) NSURL *parentPath;
+@property (nonatomic, readwrite, copy) UTMConfiguration *config;
+@property (nonatomic, readwrite) UTMViewState *viewState;
+@property (nonatomic) UTMLogging *logging;
+@property (nonatomic, readwrite) BOOL busy;
+@property (nonatomic, readwrite, nullable) UTMScreenshot *screenshot;
+@property (nonatomic, assign, readwrite) UTMVMState state;
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+- (nullable instancetype)initWithURL:(NSURL *)url;
+- (instancetype)initWithConfiguration:(UTMConfiguration *)configuration withDestinationURL:(NSURL *)dstUrl;
+
+- (NSURL *)packageURLForName:(NSString *)name;
+- (void)changeState:(UTMVMState)state;
+- (void)errorTriggered:(nullable NSString *)msg;
+- (BOOL)loadConfigurationWithReload:(BOOL)reload error:(NSError * _Nullable __autoreleasing *)err;
+- (BOOL)saveConfigurationWithError:(NSError * _Nullable __autoreleasing *)err;
+
+- (NSDictionary *)loadPlist:(NSURL *)path withError:(NSError **)err;
+- (BOOL)savePlist:(NSURL *)path dict:(NSDictionary *)dict withError:(NSError **)err;
+- (void)syncViewState;
+- (void)restoreViewState;
+- (void)loadViewState;
+- (void)saveViewState;
+- (void)loadScreenshot;
+- (void)saveScreenshot;
+- (void)deleteScreenshot;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 4 - 17
Managers/UTMVirtualMachine.h

@@ -18,40 +18,29 @@
 #import "UTMVirtualMachineDelegate.h"
 #import "CSConnectionDelegate.h"
 #import "UTMRenderSource.h"
-#import "UTMQemuManagerDelegate.h"
 #import "UTMInputOutput.h"
 
 @class UTMConfiguration;
 @class UTMLogging;
-@class UTMQemuManager;
 @class UTMScreenshot;
 
-typedef NS_ENUM(NSInteger, UTMDisplayType) {
-    UTMDisplayTypeFullGraphic,
-    UTMDisplayTypeConsole
-};
-
 NS_ASSUME_NONNULL_BEGIN
 
-@interface UTMVirtualMachine : NSObject<UTMQemuManagerDelegate>
+@interface UTMVirtualMachine : NSObject
 
 @property (nonatomic, readonly, nullable) NSURL *path;
 @property (nonatomic, weak, nullable) id<UTMVirtualMachineDelegate> delegate;
-@property (nonatomic, weak, nullable) id ioDelegate;
-@property (nonatomic, strong) NSURL *parentPath;
-@property (nonatomic, readonly, copy) UTMConfiguration *configuration;
+@property (nonatomic, readonly, copy) UTMConfiguration *config;
 @property (nonatomic, readonly) UTMViewState *viewState;
 @property (nonatomic, assign, readonly) UTMVMState state;
-@property (nonatomic, readonly) BOOL busy;
 @property (nonatomic, readonly, nullable) UTMScreenshot *screenshot;
 
 + (BOOL)URLisVirtualMachine:(NSURL *)url NS_SWIFT_NAME(isVirtualMachine(url:));
 + (NSString *)virtualMachineName:(NSURL *)url;
 + (NSURL *)virtualMachinePath:(NSString *)name inParentURL:(NSURL *)parent;
 
-- (instancetype)init NS_DESIGNATED_INITIALIZER;
-- (nullable instancetype)initWithURL:(NSURL *)url;
-- (instancetype)initWithConfiguration:(UTMConfiguration *)configuration withDestinationURL:(NSURL *)dstUrl;
++ (nullable UTMVirtualMachine *)virtualMachineWithURL:(NSURL *)url;
++ (UTMVirtualMachine *)virtualMachineWithConfiguration:(UTMConfiguration *)configuration withDestinationURL:(NSURL *)dstUrl;
 
 - (BOOL)reloadConfigurationWithError:(NSError * _Nullable *)err;
 - (BOOL)saveUTMWithError:(NSError * _Nullable *)err;
@@ -65,8 +54,6 @@ NS_ASSUME_NONNULL_BEGIN
 - (BOOL)deleteSaveVM;
 - (BOOL)resumeVM;
 
-- (UTMDisplayType)supportedDisplayType;
-
 @end
 
 NS_ASSUME_NONNULL_END

+ 44 - 440
Managers/UTMVirtualMachine.m

@@ -14,81 +14,29 @@
 // limitations under the License.
 //
 
-#import <TargetConditionals.h>
 #import "UTMVirtualMachine.h"
-#import "UTMVirtualMachine+Drives.h"
-#import "UTMVirtualMachine+SPICE.h"
+#import "UTMVirtualMachine-Private.h"
+#import "UTMQemuVirtualMachine.h"
 #import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Display.h"
 #import "UTMConfiguration+Drives.h"
-#import "UTMConfiguration+Miscellaneous.h"
-#import "UTMViewState.h"
-#import "UTMQemuManager.h"
-#import "UTMQemuSystem.h"
-#import "UTMTerminalIO.h"
-#import "UTMSpiceIO.h"
 #import "UTMLogging.h"
 #import "UTMScreenshot.h"
-#import "UTMPortAllocator.h"
-#import "qapi-events.h"
-
-const int kQMPMaxConnectionTries = 30; // qemu needs to start spice server first
-const int64_t kStopTimeout = (int64_t)30*NSEC_PER_SEC;
+#import "UTMViewState.h"
 
 NSString *const kUTMErrorDomain = @"com.utmapp.utm";
 NSString *const kUTMBundleConfigFilename = @"config.plist";
 NSString *const kUTMBundleExtension = @"utm";
 NSString *const kUTMBundleViewFilename = @"view.plist";
 NSString *const kUTMBundleScreenshotFilename = @"screenshot.png";
-NSString *const kSuspendSnapshotName = @"suspend";
-
-
-@interface UTMVirtualMachine ()
 
-@property (nonatomic, readwrite, nullable) NSURL *path;
-@property (nonatomic, readwrite, copy) UTMConfiguration *configuration;
-@property (nonatomic, readonly) UTMQemuManager *qemu;
-@property (nonatomic, readwrite, nullable) UTMQemuSystem *system;
-@property (nonatomic, readwrite) UTMViewState *viewState;
-@property (nonatomic) UTMLogging *logging;
-@property (nonatomic, readonly, nullable) id<UTMInputOutput> ioService;
-@property (nonatomic, readwrite) BOOL busy;
-@property (nonatomic, readwrite, nullable) UTMScreenshot *screenshot;
-
-@end
-
-@implementation UTMVirtualMachine {
-    dispatch_semaphore_t _will_quit_sema;
-    dispatch_semaphore_t _qemu_exit_sema;
-}
+@implementation UTMVirtualMachine
 
 - (void)setDelegate:(id<UTMVirtualMachineDelegate>)delegate {
     _delegate = delegate;
-    _delegate.vmConfiguration = self.configuration;
+    _delegate.vmConfiguration = self.config;
     [self restoreViewState];
 }
 
-- (id)ioDelegate {
-    if ([self.ioService isKindOfClass:[UTMSpiceIO class]]) {
-        return ((UTMSpiceIO *)self.ioService).delegate;
-    } else if ([self.ioService isKindOfClass:[UTMTerminalIO class]]) {
-        return ((UTMTerminalIO *)self.ioService).terminal.delegate;
-    } else {
-        return nil;
-    }
-}
-
-- (void)setIoDelegate:(id)ioDelegate {
-    if ([self.ioService isKindOfClass:[UTMSpiceIO class]]) {
-        ((UTMSpiceIO *)self.ioService).delegate = ioDelegate;
-    } else if ([self.ioService isKindOfClass:[UTMTerminalIO class]]) {
-        ((UTMTerminalIO *)self.ioService).terminal.delegate = ioDelegate;
-    } else if (self.state == kVMStarted) {
-        NSAssert(0, @"ioService class is invalid: %@", NSStringFromClass([self.ioService class]));
-    }
-}
-
 + (BOOL)URLisVirtualMachine:(NSURL *)url {
     return [url.pathExtension isEqualToString:kUTMBundleExtension];
 }
@@ -101,11 +49,17 @@ NSString *const kSuspendSnapshotName = @"suspend";
     return [[parent URLByAppendingPathComponent:name] URLByAppendingPathExtension:kUTMBundleExtension];
 }
 
++ (nullable UTMVirtualMachine *)virtualMachineWithURL:(NSURL *)url {
+    return [[UTMQemuVirtualMachine alloc] initWithURL:url];
+}
+
++ (UTMVirtualMachine *)virtualMachineWithConfiguration:(UTMConfiguration *)configuration withDestinationURL:(NSURL *)dstUrl {
+    return [[UTMQemuVirtualMachine alloc] initWithConfiguration:configuration withDestinationURL:dstUrl];
+}
+
 - (instancetype)init {
     self = [super init];
     if (self) {
-        _will_quit_sema = dispatch_semaphore_create(0);
-        _qemu_exit_sema = dispatch_semaphore_create(0);
 #if TARGET_OS_IPHONE
         self.logging = [UTMLogging sharedInstance];
 #else
@@ -127,9 +81,9 @@ NSString *const kSuspendSnapshotName = @"suspend";
         [self loadViewState];
         [self loadScreenshot];
         if (self.viewState.suspended) {
-            _state = kVMSuspended;
+            self.state = kVMSuspended;
         } else {
-            _state = kVMStopped;
+            self.state = kVMStopped;
         }
     }
     return self;
@@ -139,7 +93,7 @@ NSString *const kSuspendSnapshotName = @"suspend";
     self = [self init];
     if (self) {
         self.parentPath = dstUrl;
-        self.configuration = configuration;
+        self.config = configuration;
         self.viewState = [[UTMViewState alloc] init];
     }
     return self;
@@ -147,7 +101,7 @@ NSString *const kSuspendSnapshotName = @"suspend";
 
 - (void)changeState:(UTMVMState)state {
     @synchronized (self) {
-        _state = state;
+        self.state = state;
         dispatch_async(dispatch_get_main_queue(), ^{
             [self.delegate virtualMachine:self transitionToState:state];
         });
@@ -160,19 +114,6 @@ NSString *const kSuspendSnapshotName = @"suspend";
 }
 
 - (BOOL)loadConfigurationWithReload:(BOOL)reload error:(NSError * _Nullable __autoreleasing *)err {
-    NSAssert(self.path != nil, @"Cannot load configuration on an unsaved VM.");
-    NSString *name = [UTMVirtualMachine virtualMachineName:self.path];
-    NSDictionary *plist = [self loadPlist:[self.path URLByAppendingPathComponent:kUTMBundleConfigFilename] withError:err];
-    if (!plist) {
-        UTMLog(@"Failed to parse config for %@, error: %@", self.path, err ? *err : nil);
-        return NO;
-    }
-    if (reload) {
-        NSAssert(self.configuration != nil, @"Trying to reload when no configuration is loaded.");
-        [self.configuration reloadConfigurationWithDictionary:plist name:name path:self.path];
-    } else {
-        self.configuration = [[UTMConfiguration alloc] initWithDictionary:plist name:name path:self.path];
-    }
     return YES;
 }
 
@@ -180,23 +121,27 @@ NSString *const kSuspendSnapshotName = @"suspend";
     return [self loadConfigurationWithReload:YES error:err];
 }
 
+- (BOOL)saveConfigurationWithError:(NSError * _Nullable __autoreleasing *)err {
+    return YES;
+}
+
 - (BOOL)saveUTMWithError:(NSError * _Nullable *)err {
     NSFileManager *fileManager = [NSFileManager defaultManager];
-    NSURL *url = [self packageURLForName:self.configuration.name];
+    NSURL *url = [self packageURLForName:self.config.name];
     __block NSError *_err;
-    if (!self.configuration.existingPath) { // new package
+    if (!self.config.existingPath) { // new package
         if (![fileManager createDirectoryAtURL:url withIntermediateDirectories:YES attributes:nil error:&_err]) {
             goto error;
         }
-    } else if (![self.configuration.existingPath.URLByStandardizingPath isEqual:url.URLByStandardizingPath]) { // rename if needed
-        if (![fileManager moveItemAtURL:self.configuration.existingPath toURL:url error:&_err]) {
+    } else if (![self.config.existingPath.URLByStandardizingPath isEqual:url.URLByStandardizingPath]) { // rename if needed
+        if (![fileManager moveItemAtURL:self.config.existingPath toURL:url error:&_err]) {
             goto error;
         }
     }
     // save icon
-    if (self.configuration.iconCustom && self.configuration.selectedCustomIconPath) {
-        NSURL *oldIconPath = [url URLByAppendingPathComponent:self.configuration.icon];
-        NSString *newIcon = self.configuration.selectedCustomIconPath.lastPathComponent;
+    if (self.config.iconCustom && self.config.selectedCustomIconPath) {
+        NSURL *oldIconPath = [url URLByAppendingPathComponent:self.config.icon];
+        NSString *newIcon = self.config.selectedCustomIconPath.lastPathComponent;
         NSURL *newIconPath = [url URLByAppendingPathComponent:newIcon];
         
         // delete old icon
@@ -204,28 +149,26 @@ NSString *const kSuspendSnapshotName = @"suspend";
             [fileManager removeItemAtURL:oldIconPath error:&_err]; // ignore error
         }
         // copy new icon
-        if (![fileManager copyItemAtURL:self.configuration.selectedCustomIconPath toURL:newIconPath error:&_err]) {
+        if (![fileManager copyItemAtURL:self.config.selectedCustomIconPath toURL:newIconPath error:&_err]) {
             goto error;
         }
         // commit icon
-        self.configuration.icon = newIcon;
-        self.configuration.selectedCustomIconPath = nil;
+        self.config.icon = newIcon;
+        self.config.selectedCustomIconPath = nil;
     }
     // save config
-    if (![self savePlist:[url URLByAppendingPathComponent:kUTMBundleConfigFilename]
-                    dict:self.configuration.dictRepresentation
-               withError:err]) {
+    if (![self saveConfigurationWithError:err]) {
         return NO;
     }
     // create disk images directory
-    if (!self.configuration.existingPath) {
+    if (!self.config.existingPath) {
         NSURL *dstPath = [url URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
         NSURL *tmpPath = [fileManager.temporaryDirectory URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
         
         // create images directory
         if ([fileManager fileExistsAtPath:tmpPath.path]) {
             // delete any orphaned images
-            NSArray<NSString *> *orphans = self.configuration.orphanedDrives;
+            NSArray<NSString *> *orphans = self.config.orphanedDrives;
             for (NSInteger i = 0; i < orphans.count; i++) {
                 NSURL *orphanPath = [tmpPath URLByAppendingPathComponent:orphans[i]];
                 UTMLog(@"Deleting orphaned image '%@'", orphans[i]);
@@ -244,7 +187,7 @@ NSString *const kSuspendSnapshotName = @"suspend";
             }
         }
     }
-    self.configuration.existingPath = url;
+    self.config.existingPath = url;
     self.path = url;
     return YES;
 error:
@@ -266,77 +209,10 @@ error:
     }
 }
 
-- (BOOL)startVM {
-    @synchronized (self) {
-        if (self.busy || (self.state != kVMStopped && self.state != kVMSuspended)) {
-            return NO; // already started
-        } else {
-            self.busy = YES;
-        }
-    }
-    // start logging
-    if (self.configuration.debugLogEnabled) {
-        [self.logging logToFile:[self.path URLByAppendingPathComponent:[UTMConfiguration debugLogName]]];
-    }
-    
-    if (!self.system) {
-        self.system = [[UTMQemuSystem alloc] initWithConfiguration:self.configuration imgPath:self.path];
-        self.system.logging = self.logging;
-#if !TARGET_OS_IPHONE
-        [self.system setupXpc];
-#endif
-        self.system.qmpPort = [[UTMPortAllocator sharedInstance] allocatePort];
-        self.system.spicePort = [[UTMPortAllocator sharedInstance] allocatePort];
-        _qemu = [[UTMQemuManager alloc] initWithPort:self.system.qmpPort];
-        _qemu.delegate = self;
-    }
+#define notImplemented @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass.", __PRETTY_FUNCTION__] userInfo:nil]
 
-    if (!self.system) {
-        [self errorTriggered:NSLocalizedString(@"Internal error starting VM.", @"UTMVirtualMachine")];
-        self.busy = NO;
-        return NO;
-    }
-    
-    if (!_ioService) {
-        _ioService = [self inputOutputServiceWithPort:self.system.spicePort];
-    }
-    
-    self.delegate.vmMessage = nil;
-    [self changeState:kVMStarting];
-    if (self.configuration.debugLogEnabled) {
-        [_ioService setDebugMode:YES];
-    }
-    
-    BOOL ioStatus = [_ioService startWithError: nil];
-    if (!ioStatus) {
-        [self errorTriggered:NSLocalizedString(@"Internal error starting main loop.", @"UTMVirtualMachine")];
-        self.busy = NO;
-        return NO;
-    }
-    if (self.viewState.suspended) {
-        self.system.snapshot = kSuspendSnapshotName;
-    }
-    [self.system startWithCompletion:^(BOOL success, NSString *msg){
-        if (!success) {
-            [self errorTriggered:msg];
-        }
-        dispatch_semaphore_signal(self->_qemu_exit_sema);
-    }];
-    [self->_ioService connectWithCompletion:^(BOOL success, NSString * _Nullable msg) {
-        if (!success) {
-            [self errorTriggered:msg];
-        } else {
-            [self changeState:kVMStarted];
-            [self restoreViewState];
-            if (self.viewState.suspended) {
-                [self deleteSaveVM];
-            }
-        }
-    }];
-    self->_qemu.retries = kQMPMaxConnectionTries;
-    [self->_qemu connect];
-    self.busy = NO;
-    return YES;
+- (BOOL)startVM {
+    notImplemented;
 }
 
 - (BOOL)quitVM {
@@ -344,296 +220,27 @@ error:
 }
 
 - (BOOL)quitVMForce:(BOOL)force {
-    @synchronized (self) {
-        if (!force && (self.busy || self.state != kVMStarted)) {
-            return NO; // already stopping
-        } else {
-            self.busy = YES;
-        }
-    }
-    self.viewState.suspended = NO;
-    [self syncViewState];
-    if (!force) {
-        [self changeState:kVMStopping];
-    }
-    // save view settings early to win exit race
-    [self saveViewState];
-    
-    _qemu.retries = 0;
-    [_qemu vmQuitWithCompletion:nil];
-    if (force || dispatch_semaphore_wait(_will_quit_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Stop operation timeout or force quit");
-    }
-    [_qemu disconnect];
-    _qemu.delegate = nil;
-    _qemu = nil;
-    [_ioService disconnect];
-    _ioService = nil;
-    
-    if (force || dispatch_semaphore_wait(_qemu_exit_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Exit operation timeout or force quit");
-    }
-    [self.system stopQemu];
-    if (self.system.qmpPort) {
-        [[UTMPortAllocator sharedInstance] freePort:self.system.qmpPort];
-        self.system.qmpPort = 0;
-    }
-    if (self.system.spicePort) {
-        [[UTMPortAllocator sharedInstance] freePort:self.system.spicePort];
-        self.system.spicePort = 0;
-    }
-    self.system = nil;
-    [self changeState:kVMStopped];
-    // stop logging
-    [self.logging endLog];
-    self.busy = NO;
-    return YES;
+    notImplemented;
 }
 
 - (BOOL)resetVM {
-    @synchronized (self) {
-        if (self.busy || (self.state != kVMStarted && self.state != kVMPaused)) {
-            return NO; // already stopping
-        } else {
-            self.busy = YES;
-        }
-    }
-    [self syncViewState];
-    [self changeState:kVMStopping];
-    if (self.viewState.suspended) {
-        [self deleteSaveVM];
-    }
-    [self saveViewState];
-    __block BOOL success = YES;
-    dispatch_semaphore_t reset_sema = dispatch_semaphore_create(0);
-    [_qemu vmResetWithCompletion:^(NSError *err) {
-        UTMLog(@"reset callback: err? %@", err);
-        if (err) {
-            UTMLog(@"error: %@", err);
-            success = NO;
-        }
-        dispatch_semaphore_signal(reset_sema);
-    }];
-    if (dispatch_semaphore_wait(reset_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Reset operation timeout");
-        success = NO;
-    }
-    if (success) {
-        [self changeState:kVMStarted];
-    } else {
-        [self changeState:kVMError];
-    }
-    self.busy = NO;
-    return success;
+    notImplemented;
 }
 
 - (BOOL)pauseVM {
-    @synchronized (self) {
-        if (self.busy || self.state != kVMStarted) {
-            return NO; // already stopping
-        } else {
-            self.busy = YES;
-        }
-    }
-    [self syncViewState];
-    [self changeState:kVMPausing];
-    [self saveScreenshot];
-    __block BOOL success = YES;
-    dispatch_semaphore_t suspend_sema = dispatch_semaphore_create(0);
-    [_qemu vmStopWithCompletion:^(NSError * err) {
-        UTMLog(@"stop callback: err? %@", err);
-        if (err) {
-            UTMLog(@"error: %@", err);
-            success = NO;
-        }
-        dispatch_semaphore_signal(suspend_sema);
-    }];
-    if (dispatch_semaphore_wait(suspend_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Stop operation timeout");
-        success = NO;
-    }
-    if (success) {
-        [self changeState:kVMPaused];
-    } else {
-        [self changeState:kVMError];
-    }
-    self.busy = NO;
-    return success;
+    notImplemented;
 }
 
 - (BOOL)saveVM {
-    @synchronized (self) {
-        if (self.busy || (self.state != kVMPaused && self.state != kVMStarted)) {
-            return NO;
-        } else {
-            self.busy = YES;
-        }
-    }
-    UTMVMState state = self.state;
-    [self changeState:kVMPausing];
-    __block BOOL success = YES;
-    dispatch_semaphore_t save_sema = dispatch_semaphore_create(0);
-    [_qemu vmSaveWithCompletion:^(NSString *result, NSError *err) {
-        UTMLog(@"save callback: %@", result);
-        if (err) {
-            UTMLog(@"error: %@", err);
-            success = NO;
-        } else if ([result localizedCaseInsensitiveContainsString:@"Error"]) {
-            UTMLog(@"save result: %@", result);
-            success = NO; // error message
-        }
-        dispatch_semaphore_signal(save_sema);
-    } snapshotName:kSuspendSnapshotName];
-    if (dispatch_semaphore_wait(save_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Save operation timeout");
-        success = NO;
-    } else if (success) {
-        UTMLog(@"Save completed");
-        self.viewState.suspended = YES;
-        [self saveViewState];
-        [self saveScreenshot];
-    }
-    [self changeState:state];
-    self.busy = NO;
-    return success;
+    notImplemented;
 }
 
 - (BOOL)deleteSaveVM {
-    __block BOOL success = YES;
-    if (self.qemu) { // if QEMU is running
-        dispatch_semaphore_t save_sema = dispatch_semaphore_create(0);
-        [_qemu vmDeleteSaveWithCompletion:^(NSString *result, NSError *err) {
-            UTMLog(@"delete save callback: %@", result);
-            if (err) {
-                UTMLog(@"error: %@", err);
-                success = NO;
-            } else if ([result localizedCaseInsensitiveContainsString:@"Error"]) {
-                UTMLog(@"save result: %@", result);
-                success = NO; // error message
-            }
-            dispatch_semaphore_signal(save_sema);
-        } snapshotName:kSuspendSnapshotName];
-        if (dispatch_semaphore_wait(save_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-            UTMLog(@"Delete save operation timeout");
-            success = NO;
-        } else {
-            UTMLog(@"Delete save completed");
-        }
-    } // otherwise we mark as deleted
-    self.viewState.suspended = NO;
-    [self saveViewState];
-    return success;
+    notImplemented;
 }
 
 - (BOOL)resumeVM {
-    @synchronized (self) {
-        if (self.busy || self.state != kVMPaused) {
-            return NO;
-        } else {
-            self.busy = YES;
-        }
-    }
-    [self changeState:kVMResuming];
-    __block BOOL success = YES;
-    dispatch_semaphore_t resume_sema = dispatch_semaphore_create(0);
-    [_qemu vmResumeWithCompletion:^(NSError *err) {
-        UTMLog(@"resume callback: err? %@", err);
-        if (err) {
-            UTMLog(@"error: %@", err);
-            success = NO;
-        }
-        dispatch_semaphore_signal(resume_sema);
-    }];
-    if (dispatch_semaphore_wait(resume_sema, dispatch_time(DISPATCH_TIME_NOW, kStopTimeout)) != 0) {
-        UTMLog(@"Resume operation timeout");
-        success = NO;
-    }
-    if (success) {
-        [self changeState:kVMStarted];
-        [self restoreViewState];
-    } else {
-        [self changeState:kVMError];
-    }
-    if (self.viewState.suspended) {
-        [self deleteSaveVM];
-    }
-    self.busy = NO;
-    return success;
-}
-
-- (UTMDisplayType)supportedDisplayType {
-    if ([self.configuration displayConsoleOnly]) {
-        return UTMDisplayTypeConsole;
-    } else {
-        return UTMDisplayTypeFullGraphic;
-    }
-}
-
-- (id<UTMInputOutput>)inputOutputServiceWithPort:(NSInteger)port {
-    if ([self supportedDisplayType] == UTMDisplayTypeConsole) {
-        return [[UTMTerminalIO alloc] initWithConfiguration:[self.configuration copy]];
-    } else {
-        return [[UTMSpiceIO alloc] initWithConfiguration:[self.configuration copy] port:port];
-    }
-}
-
-#pragma mark - Qemu manager delegate
-
-- (void)qemuHasWakeup:(UTMQemuManager *)manager {
-    UTMLog(@"qemuHasWakeup");
-}
-
-- (void)qemuHasResumed:(UTMQemuManager *)manager {
-    UTMLog(@"qemuHasResumed");
-}
-
-- (void)qemuHasStopped:(UTMQemuManager *)manager {
-    UTMLog(@"qemuHasStopped");
-}
-
-- (void)qemuHasReset:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason {
-    UTMLog(@"qemuHasReset, reason = %s", ShutdownCause_str(reason));
-}
-
-- (void)qemuHasSuspended:(UTMQemuManager *)manager {
-    UTMLog(@"qemuHasSuspended");
-}
-
-- (void)qemuWillQuit:(UTMQemuManager *)manager guest:(BOOL)guest reason:(ShutdownCause)reason {
-    UTMLog(@"qemuWillQuit, reason = %s", ShutdownCause_str(reason));
-    dispatch_semaphore_signal(_will_quit_sema);
-    if (!self.busy) {
-        [self quitVM];
-    }
-}
-
-- (void)qemuError:(UTMQemuManager *)manager error:(NSString *)error {
-    UTMLog(@"qemuError: %@", error);
-    dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
-        [self errorTriggered:error];
-    });
-}
-
-// this is called right before we execute qmp_cont so we can setup additional option
-- (void)qemuQmpDidConnect:(UTMQemuManager *)manager {
-    UTMLog(@"qemuQmpDidConnect");
-    __autoreleasing NSError *err = nil;
-    NSString *errMsg = nil;
-    if (!self.configuration.displayConsoleOnly) {
-        if (![self startSharedDirectoryWithError:&err]) {
-            errMsg = [NSString stringWithFormat:NSLocalizedString(@"Error trying to start shared directory: %@", @"UTMVirtualMachine"), err.localizedDescription];
-            UTMLog(@"%@", errMsg);
-        }
-    }
-    if (!err && ![self restoreRemovableDrivesFromBookmarksWithError:&err]) {
-        errMsg = [NSString stringWithFormat:NSLocalizedString(@"Error trying to restore removable drives: %@", @"UTMVirtualMachine"), err.localizedDescription];
-        UTMLog(@"%@", errMsg);
-    }
-    if (errMsg) {
-        dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
-            [self errorTriggered:errMsg];
-        });
-    }
+    notImplemented;
 }
 
 #pragma mark - Plist Handling
@@ -679,14 +286,12 @@ error:
 #pragma mark - View State
 
 - (void)syncViewState {
-    [self.ioService syncViewState:self.viewState];
     self.viewState.showToolbar = self.delegate.toolbarVisible;
     self.viewState.showKeyboard = self.delegate.keyboardVisible;
 }
 
 - (void)restoreViewState {
     dispatch_async(dispatch_get_main_queue(), ^{
-        [self.ioService restoreViewState:self.viewState];
         self.delegate.toolbarVisible = self.viewState.showToolbar;
         self.delegate.keyboardVisible = self.viewState.showKeyboard;
     });
@@ -715,7 +320,6 @@ error:
 }
 
 - (void)saveScreenshot {
-    self.screenshot = [self.ioService screenshot];
     NSURL *url = [self.path URLByAppendingPathComponent:kUTMBundleScreenshotFilename];
     if (self.screenshot) {
         [self.screenshot writeToURL:url atomically:NO];

+ 1 - 3
Managers/UTMVirtualMachineExtension.swift

@@ -20,10 +20,8 @@ extension UTMVirtualMachine: Identifiable {
     public var id: String {
         if self.path != nil {
             return self.path!.path // path if we're an existing VM
-        } else if self.configuration.systemUUID != nil {
-            return self.configuration.systemUUID! // static UUID for new VM
         } else {
-            return UUID().uuidString // fallback to unique UUID
+            return self.config.uuid
         }
     }
 }

+ 2 - 2
Platform/Shared/ContentView.swift

@@ -27,7 +27,7 @@ let productName = "UTM"
 
 @available(iOS 14, macOS 11, *)
 struct ContentView: View {
-    @StateObject private var newConfiguration = UTMConfiguration()
+    @StateObject private var newConfiguration = UTMQemuConfiguration()
     @State private var editMode = false
     @EnvironmentObject private var data: UTMData
     @State private var newPopupPresented = false
@@ -49,7 +49,7 @@ struct ContentView: View {
             }.optionalSidebarFrame()
             .listStyle(SidebarListStyle())
             .navigationTitle(productName)
-            .navigationOptionalSubtitle(data.selectedVM?.configuration.name ?? "")
+            .navigationOptionalSubtitle(data.selectedVM?.config.name ?? "")
             .toolbar {
                 #if os(macOS)
                 ToolbarItem(placement: .navigation) {

+ 6 - 6
Platform/Shared/VMCardView.swift

@@ -31,15 +31,15 @@ struct VMCardView: View {
     
     var body: some View {
         HStack {
-            if vm.configuration.iconCustom {
-                Logo(logo: PlatformImage(contentsOfURL: vm.configuration.existingCustomIconURL))
+            if vm.config.iconCustom {
+                Logo(logo: PlatformImage(contentsOfURL: vm.config.existingCustomIconURL))
             } else {
-                Logo(logo: PlatformImage(contentsOfURL: vm.configuration.existingIconURL))
+                Logo(logo: PlatformImage(contentsOfURL: vm.config.existingIconURL))
             }
             VStack(alignment: .leading) {
-                Text(vm.configuration.name)
+                Text(vm.config.name)
                     .font(.headline)
-                Text(vm.configuration.systemTargetPretty)
+                Text(vm.config.subtitle)
                     .font(.subheadline)
             }.lineLimit(1)
             .truncationMode(.tail)
@@ -103,6 +103,6 @@ struct Logo: View {
 @available(iOS 14, macOS 11, *)
 struct VMCardView_Previews: PreviewProvider {
     static var previews: some View {
-        VMCardView(vm: UTMVirtualMachine(configuration: UTMConfiguration(), withDestinationURL: URL(fileURLWithPath: "/")))
+        VMCardView(vm: UTMVirtualMachine(configuration: UTMQemuConfiguration(), withDestinationURL: URL(fileURLWithPath: "/")))
     }
 }

+ 8 - 8
Platform/Shared/VMConfigDisplayView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigDisplayView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     #if os(macOS)
     let displayTypePickerStyle = RadioGroupPickerStyle()
@@ -33,7 +33,7 @@ struct VMConfigDisplayView: View {
                     Text("Full Graphics").tag(false)
                     Text("Console Only").tag(true)
                 }.pickerStyle(displayTypePickerStyle)
-                .disabled(UTMConfiguration.supportedDisplayCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
+                .disabled(UTMQemuConfiguration.supportedDisplayCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
                 .onChange(of: config.displayConsoleOnly) { newConsoleOnly in
                     if newConsoleOnly {
                         if config.shareClipboardEnabled {
@@ -52,8 +52,8 @@ struct VMConfigDisplayView: View {
                     }
 
                     Section(header: Text("Style"), footer: EmptyView().padding(.bottom)) {
-                        VMConfigStringPicker(selection: $config.consoleTheme, label: Text("Theme"), rawValues: UTMConfiguration.supportedConsoleThemes(), displayValues: UTMConfiguration.supportedConsoleThemes())
-                        VMConfigStringPicker(selection: $config.consoleFont, label: Text("Font"), rawValues: UTMConfiguration.supportedConsoleFonts(), displayValues: UTMConfiguration.supportedConsoleFonts())
+                        VMConfigStringPicker(selection: $config.consoleTheme, label: Text("Theme"), rawValues: UTMQemuConfiguration.supportedConsoleThemes(), displayValues: UTMQemuConfiguration.supportedConsoleThemes())
+                        VMConfigStringPicker(selection: $config.consoleFont, label: Text("Font"), rawValues: UTMQemuConfiguration.supportedConsoleFonts(), displayValues: UTMQemuConfiguration.supportedConsoleFonts())
                         HStack {
                             Stepper(value: fontSizeObserver, in: 1...72) {
                                     Text("Font Size")
@@ -72,7 +72,7 @@ struct VMConfigDisplayView: View {
                     }
                 } else {
                     Section(header: Text("Hardware"), footer: EmptyView().padding(.bottom)) {
-                        VMConfigStringPicker(selection: $config.displayCard, label: Text("Emulated Display Card"), rawValues: UTMConfiguration.supportedDisplayCards(forArchitecture: config.systemArchitecture), displayValues: UTMConfiguration.supportedDisplayCards(forArchitecturePretty: config.systemArchitecture))
+                        VMConfigStringPicker(selection: $config.displayCard, label: Text("Emulated Display Card"), rawValues: UTMQemuConfiguration.supportedDisplayCards(forArchitecture: config.systemArchitecture), displayValues: UTMQemuConfiguration.supportedDisplayCards(forArchitecturePretty: config.systemArchitecture))
                     }
                     
                     Section(header: Text("Resolution"), footer: Text("Requires SPICE guest agent tools to be installed. Retina Mode is recommended only if the guest OS supports HiDPI.").padding(.bottom)) {
@@ -85,8 +85,8 @@ struct VMConfigDisplayView: View {
                     }
                     
                     Section(header: Text("Scaling"), footer: EmptyView().padding(.bottom)) {
-                        VMConfigStringPicker(selection: $config.displayUpscaler, label: Text("Upscaling"), rawValues: UTMConfiguration.supportedScalers(), displayValues: UTMConfiguration.supportedScalersPretty())
-                        VMConfigStringPicker(selection: $config.displayDownscaler, label: Text("Downscaling"), rawValues: UTMConfiguration.supportedScalers(), displayValues: UTMConfiguration.supportedScalersPretty())
+                        VMConfigStringPicker(selection: $config.displayUpscaler, label: Text("Upscaling"), rawValues: UTMQemuConfiguration.supportedScalers(), displayValues: UTMQemuConfiguration.supportedScalersPretty())
+                        VMConfigStringPicker(selection: $config.displayDownscaler, label: Text("Downscaling"), rawValues: UTMQemuConfiguration.supportedScalers(), displayValues: UTMQemuConfiguration.supportedScalersPretty())
                     }
                 }
             }
@@ -96,7 +96,7 @@ struct VMConfigDisplayView: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigDisplayView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigDisplayView(config: config)

+ 1 - 1
Platform/Shared/VMConfigDriveCreateView.swift

@@ -32,7 +32,7 @@ struct VMConfigDriveCreateView: View {
             }).onChange(of: driveImage.removable) { removable in
                 driveImage.reset(forSystemTarget: target, removable: removable)
             }
-            VMConfigStringPicker(selection: $driveImage.interface, label: Text("Interface"), rawValues: UTMConfiguration.supportedDriveInterfaces(), displayValues: UTMConfiguration.supportedDriveInterfacesPretty())
+            VMConfigStringPicker(selection: $driveImage.interface, label: Text("Interface"), rawValues: UTMQemuConfiguration.supportedDriveInterfaces(), displayValues: UTMQemuConfiguration.supportedDriveInterfacesPretty())
             if !driveImage.removable {
                 HStack {
                     Text("Size")

+ 4 - 4
Platform/Shared/VMConfigDriveDetailsView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigDriveDetailsView: View {
-    @ObservedObject private var config: UTMConfiguration
+    @ObservedObject private var config: UTMQemuConfiguration
     @Binding private var removable: Bool
     @Binding private var name: String?
     @Binding private var imageTypeString: String?
@@ -34,7 +34,7 @@ struct VMConfigDriveDetailsView: View {
         }
     }
     
-    init(config: UTMConfiguration, index: Int) {
+    init(config: UTMQemuConfiguration, index: Int) {
         self.config = config // for observing updates
         self._removable = Binding<Bool> {
             return config.driveRemovable(for: index)
@@ -76,9 +76,9 @@ struct VMConfigDriveDetailsView: View {
                         .multilineTextAlignment(.trailing)
                 }
             }
-            VMConfigStringPicker(selection: $imageTypeString, label: Text("Image Type"), rawValues: UTMConfiguration.supportedImageTypes(), displayValues: UTMConfiguration.supportedImageTypesPretty())
+            VMConfigStringPicker(selection: $imageTypeString, label: Text("Image Type"), rawValues: UTMQemuConfiguration.supportedImageTypes(), displayValues: UTMQemuConfiguration.supportedImageTypesPretty())
             if imageType == .disk || imageType == .CD {
-                VMConfigStringPicker(selection: $interface, label: Text("Interface"), rawValues: UTMConfiguration.supportedDriveInterfaces(), displayValues: UTMConfiguration.supportedDriveInterfacesPretty())
+                VMConfigStringPicker(selection: $interface, label: Text("Interface"), rawValues: UTMQemuConfiguration.supportedDriveInterfaces(), displayValues: UTMQemuConfiguration.supportedDriveInterfacesPretty())
             }
         }
     }

+ 2 - 2
Platform/Shared/VMConfigInfoView.swift

@@ -28,7 +28,7 @@ private enum IconStyle: String, Identifiable, CaseIterable {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigInfoView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var imageSelectVisible: Bool = false
     @State private var iconStyle: IconStyle = .generic
     @State private var warningMessage: String? = nil
@@ -231,7 +231,7 @@ private struct IconSelect: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigInfoView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         Group {

+ 2 - 2
Platform/Shared/VMConfigInputView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigInputView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         VStack {
@@ -65,7 +65,7 @@ struct GestureSettingsSection: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigInputView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigInputView(config: config)

+ 7 - 7
Platform/Shared/VMConfigNetworkView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigNetworkView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var showAdvanced: Bool = false
     
     var body: some View {
@@ -40,9 +40,9 @@ struct VMConfigNetworkView: View {
                     })
                     #endif
                     if config.networkEnabled {
-                        VMConfigStringPicker(selection: $config.networkCard, label: Text("Emulated Network Card"), rawValues: UTMConfiguration.supportedNetworkCards(forArchitecture: config.systemArchitecture), displayValues: UTMConfiguration.supportedNetworkCards(forArchitecturePretty: config.systemArchitecture))
+                        VMConfigStringPicker(selection: $config.networkCard, label: Text("Emulated Network Card"), rawValues: UTMQemuConfiguration.supportedNetworkCards(forArchitecture: config.systemArchitecture), displayValues: UTMQemuConfiguration.supportedNetworkCards(forArchitecturePretty: config.systemArchitecture))
                     }
-                }.disabled(UTMConfiguration.supportedNetworkCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
+                }.disabled(UTMQemuConfiguration.supportedNetworkCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
                 
                 if config.networkEnabled {
                     Toggle(isOn: $showAdvanced.animation(), label: {
@@ -66,10 +66,10 @@ struct VMConfigNetworkView: View {
 
 @available(iOS 14, macOS 11, *)
 struct NetworkModeSection: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
-        VMConfigStringPicker(selection: $config.networkMode, label: Text("Network Mode"), rawValues: UTMConfiguration.supportedNetworkModes(), displayValues: UTMConfiguration.supportedNetworkModesPretty())
+        VMConfigStringPicker(selection: $config.networkMode, label: Text("Network Mode"), rawValues: UTMQemuConfiguration.supportedNetworkModes(), displayValues: UTMQemuConfiguration.supportedNetworkModesPretty())
         if config.networkMode == "bridged" {
             HStack {
                 Text("Bridged Interface")
@@ -83,7 +83,7 @@ struct NetworkModeSection: View {
 
 @available(iOS 14, macOS 11, *)
 struct IPConfigurationSection: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         Section(header: Text("IP Configuration"), footer: EmptyView().padding(.bottom)) {
@@ -158,7 +158,7 @@ struct IPConfigurationSection: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigNetworkingView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigNetworkView(config: config)

+ 4 - 4
Platform/Shared/VMConfigPortForwardForm.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigPortForwardForm: View {
-    @ObservedObject var configPort: UTMConfigurationPortForward
+    @ObservedObject var configPort: UTMQemuConfigurationPortForward
     
     var body: some View {
         Group {
@@ -53,8 +53,8 @@ struct VMConfigPortForwardForm: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigPortForwardForm_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
-    @State static private var configPort = UTMConfigurationPortForward()
+    @State static private var config = UTMQemuConfiguration()
+    @State static private var configPort = UTMQemuConfigurationPortForward()
     
     static var previews: some View {
         VStack {
@@ -65,7 +65,7 @@ struct VMConfigPortForwardForm_Previews: PreviewProvider {
             }
         }.onAppear {
             if config.countPortForwards == 0 {
-                let newConfigPort = UTMConfigurationPortForward()
+                let newConfigPort = UTMQemuConfigurationPortForward()
                 newConfigPort.protocol = "tcp"
                 newConfigPort.guestAddress = "1.2.3.4"
                 newConfigPort.guestPort = 1234

+ 4 - 4
Platform/Shared/VMConfigQEMUView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigQEMUView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var newArg: String = ""
     @State private var showExportLog: Bool = false
     @State private var showExportArgs: Bool = false
@@ -28,7 +28,7 @@ struct VMConfigQEMUView: View {
         guard let path = config.existingPath else {
             return false
         }
-        let logPath = path.appendingPathComponent(UTMConfiguration.debugLogName())
+        let logPath = path.appendingPathComponent(UTMQemuConfiguration.debugLogName())
         return FileManager.default.fileExists(atPath: logPath.path)
     }
     
@@ -124,7 +124,7 @@ struct VMConfigQEMUView: View {
 
 @available(iOS 14, macOS 11, *)
 struct CustomArguments: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         ForEach(0..<config.countArguments, id: \.self) { i in
@@ -172,7 +172,7 @@ struct CustomArguments: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigQEMUView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigQEMUView(config: config)

+ 2 - 2
Platform/Shared/VMConfigSharingView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigSharingView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         VStack {
@@ -93,7 +93,7 @@ struct VMConfigSharingView: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigSharingView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigSharingView(config: config)

+ 4 - 4
Platform/Shared/VMConfigSoundView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigSoundView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         VStack {
@@ -28,9 +28,9 @@ struct VMConfigSoundView: View {
                         Text("Enabled")
                     })
                     if config.soundEnabled {
-                        VMConfigStringPicker(selection: $config.soundCard, label: Text("Emulated Audio Card"), rawValues: UTMConfiguration.supportedSoundCards(forArchitecture: config.systemArchitecture), displayValues: UTMConfiguration.supportedSoundCards(forArchitecturePretty: config.systemArchitecture))
+                        VMConfigStringPicker(selection: $config.soundCard, label: Text("Emulated Audio Card"), rawValues: UTMQemuConfiguration.supportedSoundCards(forArchitecture: config.systemArchitecture), displayValues: UTMQemuConfiguration.supportedSoundCards(forArchitecturePretty: config.systemArchitecture))
                     }
-                }.disabled(UTMConfiguration.supportedSoundCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
+                }.disabled(UTMQemuConfiguration.supportedSoundCards(forArchitecture: config.systemArchitecture)?.isEmpty ?? true)
             }
         }
     }
@@ -38,7 +38,7 @@ struct VMConfigSoundView: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigSoundView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigSoundView(config: config)

+ 14 - 14
Platform/Shared/VMConfigSystemView.swift

@@ -27,7 +27,7 @@ struct VMConfigSystemView: View {
     let warningThreshold = 0.4
     #endif
     
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var showAdvanced: Bool = false
     @State private var warningMessage: String? = nil
     
@@ -40,7 +40,7 @@ struct VMConfigSystemView: View {
                 })
                 if showAdvanced {
                     Section(header: Text("CPU")) {
-                        VMConfigStringPicker(selection: $config.systemCPU.animation(), label: EmptyView(), rawValues: UTMConfiguration.supportedCpus(forArchitecture: config.systemArchitecture), displayValues: UTMConfiguration.supportedCpus(forArchitecturePretty: config.systemArchitecture))
+                        VMConfigStringPicker(selection: $config.systemCPU.animation(), label: EmptyView(), rawValues: UTMQemuConfiguration.supportedCpus(forArchitecture: config.systemArchitecture), displayValues: UTMQemuConfiguration.supportedCpus(forArchitecturePretty: config.systemArchitecture))
                     }
                     CPUFlagsOptions(config: config)
                     Section(header: Text("CPU Cores"), footer: Text("Set to 0 to use maximum supported CPUs. Force multicore might result in incorrect emulation.").padding(.bottom)) {
@@ -107,7 +107,7 @@ struct VMConfigSystemView: View {
 struct HardwareOptions: View {
     let validMemoryValues = [32, 64, 128, 256, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 32768]
     
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     let validateMemorySize: (Bool) -> Void
     @EnvironmentObject private var data: UTMData
     @State private var memorySizeIndex: Float = 0
@@ -123,19 +123,19 @@ struct HardwareOptions: View {
             }
         )
         Section(header: Text("Hardware")) {
-            VMConfigStringPicker(selection: $config.systemArchitecture, label: Text("Architecture"), rawValues: UTMConfiguration.supportedArchitectures(), displayValues: UTMConfiguration.supportedArchitecturesPretty())
+            VMConfigStringPicker(selection: $config.systemArchitecture, label: Text("Architecture"), rawValues: UTMQemuConfiguration.supportedArchitectures(), displayValues: UTMQemuConfiguration.supportedArchitecturesPretty())
                 .onChange(of: config.systemArchitecture, perform: { value in
                     guard let arch = value else {
                         return
                     }
-                    let index = UTMConfiguration.defaultTargetIndex(forArchitecture: arch)
-                    let targets = UTMConfiguration.supportedTargets(forArchitecture: arch)
+                    let index = UTMQemuConfiguration.defaultTargetIndex(forArchitecture: arch)
+                    let targets = UTMQemuConfiguration.supportedTargets(forArchitecture: arch)
                     config.systemTarget = targets?[index]
                     config.loadDefaults(forTarget: config.systemTarget, architecture: arch)
                     // disable unsupported hardware
                     if let displayCard = config.displayCard {
-                        if !UTMConfiguration.supportedDisplayCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(displayCard) == .orderedSame }) {
-                            if UTMConfiguration.supportedDisplayCards(forArchitecture: arch)!.contains("VGA") {
+                        if !UTMQemuConfiguration.supportedDisplayCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(displayCard) == .orderedSame }) {
+                            if UTMQemuConfiguration.supportedDisplayCards(forArchitecture: arch)!.contains("VGA") {
                                 config.displayCard = "VGA" // most devices support VGA
                             } else {
                                 config.displayConsoleOnly = true
@@ -145,12 +145,12 @@ struct HardwareOptions: View {
                         }
                     }
                     if let networkCard = config.networkCard {
-                        if !UTMConfiguration.supportedNetworkCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(networkCard) == .orderedSame }) {
+                        if !UTMQemuConfiguration.supportedNetworkCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(networkCard) == .orderedSame }) {
                             config.networkEnabled = false
                         }
                     }
                     if let soundCard = config.soundCard {
-                        if !UTMConfiguration.supportedSoundCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(soundCard) == .orderedSame }) {
+                        if !UTMQemuConfiguration.supportedSoundCards(forArchitecture: arch)!.contains(where: { $0.caseInsensitiveCompare(soundCard) == .orderedSame }) {
                             config.soundEnabled = false
                         }
                     }
@@ -159,7 +159,7 @@ struct HardwareOptions: View {
                 Text("The selected architecture is unsupported in this version of UTM.")
                     .foregroundColor(.red)
             }
-            VMConfigStringPicker(selection: $config.systemTarget, label: Text("System"), rawValues: UTMConfiguration.supportedTargets(forArchitecture: config.systemArchitecture), displayValues: UTMConfiguration.supportedTargets(forArchitecturePretty: config.systemArchitecture))
+            VMConfigStringPicker(selection: $config.systemTarget, label: Text("System"), rawValues: UTMQemuConfiguration.supportedTargets(forArchitecture: config.systemArchitecture), displayValues: UTMQemuConfiguration.supportedTargets(forArchitecturePretty: config.systemArchitecture))
                 .onChange(of: config.systemTarget, perform: { value in
                     config.loadDefaults(forTarget: value, architecture: config.systemArchitecture)
                 })
@@ -201,11 +201,11 @@ struct HardwareOptions: View {
 
 @available(iOS 14, macOS 11, *)
 struct CPUFlagsOptions: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var showAllFlags: Bool = false
     
     var body: some View {
-        let allFlags = UTMConfiguration.supportedCpuFlags(forArchitecture: config.systemArchitecture) ?? []
+        let allFlags = UTMQemuConfiguration.supportedCpuFlags(forArchitecture: config.systemArchitecture) ?? []
         let activeFlags = config.systemCPUFlags ?? []
         if config.systemCPU != "default" && allFlags.count > 0 {
             Section(header: Text("CPU Flags")) {
@@ -274,7 +274,7 @@ struct OptionsList<Content>: View where Content: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMConfigSystemView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMConfigSystemView(config: config)

+ 6 - 6
Platform/Shared/VMDetailsView.swift

@@ -51,10 +51,10 @@ struct VMDetailsView: View {
         } else {
             ScrollView {
                 Screenshot(vm: vm, large: regularScreenSizeClass)
-                let notes = vm.configuration.notes ?? ""
+                let notes = vm.config.notes ?? ""
                 if regularScreenSizeClass && !notes.isEmpty {
                     HStack(alignment: .top) {
-                        Details(config: vm.configuration, sessionConfig: vm.viewState, sizeLabel: sizeLabel)
+                        Details(config: vm.config, sessionConfig: vm.viewState, sizeLabel: sizeLabel)
                             .padding()
                             .frame(maxWidth: .infinity)
                         Text(notes)
@@ -67,7 +67,7 @@ struct VMDetailsView: View {
                         .padding([.leading, .trailing, .bottom])
                 } else {
                     VStack {
-                        Details(config: vm.configuration, sessionConfig: vm.viewState, sizeLabel: sizeLabel)
+                        Details(config: vm.config, sessionConfig: vm.viewState, sizeLabel: sizeLabel)
                         if !notes.isEmpty {
                             Text(notes)
                                 .font(.body)
@@ -77,10 +77,10 @@ struct VMDetailsView: View {
                     }.padding([.leading, .trailing, .bottom])
                 }
             }.labelStyle(DetailsLabelStyle())
-            .navigationTitle(vm.configuration.name)
+            .navigationTitle(vm.config.name)
             .modifier(VMToolbarModifier(vm: vm, bottom: !regularScreenSizeClass))
             .sheet(isPresented: $data.showSettingsModal) {
-                VMSettingsView(vm: vm, config: vm.configuration)
+                VMSettingsView(vm: vm, config: vm.config)
                     .environmentObject(data)
             }
         }
@@ -193,7 +193,7 @@ struct DetailsLabelStyle: LabelStyle {
 
 @available(iOS 14, macOS 11, *)
 struct VMDetailsView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMDetailsView(vm: UTMVirtualMachine(configuration: config, withDestinationURL: URL(fileURLWithPath: "")))

+ 1 - 1
Platform/Shared/VMDriveImage.swift

@@ -34,7 +34,7 @@ class VMDriveImage: ObservableObject {
     func reset(forSystemTarget target: String?, removable: Bool) {
         self.removable = removable
         self.imageType = removable ? .CD : .disk
-        self.interface = UTMConfiguration.defaultDriveInterface(forTarget: target, type: imageType)
+        self.interface = UTMQemuConfiguration.defaultDriveInterface(forTarget: target, type: imageType)
         self.size = removable ? 0 : 10240
     }
 }

+ 2 - 2
Platform/Shared/VMRemovableDrivesView.swift

@@ -34,7 +34,7 @@ struct VMRemovableDrivesView: View {
     
     init(vm: UTMVirtualMachine) {
         self.vm = vm
-        self.config = vm.configuration
+        self.config = vm.config
         self.sessionConfig = vm.viewState
     }
     
@@ -198,7 +198,7 @@ struct VMRemovableDrivesView: View {
 
 @available(iOS 14, macOS 11, *)
 struct VMRemovableDrivesView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMRemovableDrivesView(vm: UTMVirtualMachine(configuration: config, withDestinationURL: URL(fileURLWithPath: "")))

+ 15 - 11
Platform/Swift-Bridging-Header.h

@@ -16,15 +16,17 @@
 
 #include "TargetConditionals.h"
 #include "UTMConfiguration.h"
-#include "UTMConfiguration+Constants.h"
-#include "UTMConfiguration+Defaults.h"
-#include "UTMConfiguration+Display.h"
 #include "UTMConfiguration+Drives.h"
-#include "UTMConfiguration+Miscellaneous.h"
-#include "UTMConfiguration+Networking.h"
-#include "UTMConfiguration+Sharing.h"
-#include "UTMConfiguration+System.h"
-#include "UTMConfigurationPortForward.h"
+#include "UTMQemuConfiguration.h"
+#include "UTMQemuConfiguration+Constants.h"
+#include "UTMQemuConfiguration+Defaults.h"
+#include "UTMQemuConfiguration+Display.h"
+#include "UTMQemuConfiguration+Drives.h"
+#include "UTMQemuConfiguration+Miscellaneous.h"
+#include "UTMQemuConfiguration+Networking.h"
+#include "UTMQemuConfiguration+Sharing.h"
+#include "UTMQemuConfiguration+System.h"
+#include "UTMQemuConfigurationPortForward.h"
 #include "UTMDrive.h"
 #include "UTMQcow2.h"
 #include "UTMQemu.h"
@@ -33,9 +35,11 @@
 #include "UTMLogging.h"
 #include "UTMViewState.h"
 #include "UTMVirtualMachine.h"
-#include "UTMVirtualMachine+Drives.h"
-#include "UTMVirtualMachine+SPICE.h"
-#include "UTMVirtualMachine+Terminal.h"
+#include "UTMVirtualMachine+IO.h"
+#include "UTMQemuVirtualMachine.h"
+#include "UTMQemuVirtualMachine+Drives.h"
+#include "UTMQemuVirtualMachine+SPICE.h"
+#include "UTMQemuVirtualMachine+Terminal.h"
 #include "UTMRenderer.h"
 #include "UTMScreenshot.h"
 #include "UTMSpiceIO.h"

+ 15 - 15
Platform/UTMData.swift

@@ -134,7 +134,7 @@ class UTMData: ObservableObject {
         return ProcessInfo.processInfo.globallyUniqueString
     }
     
-    func newDefaultDrivePath(type: UTMDiskImageType, forConfig: UTMConfiguration) -> String {
+    func newDefaultDrivePath(type: UTMDiskImageType, forConfig: UTMQemuConfiguration) -> String {
         let nameForId = { (i: Int) in "\(type.description)-\(i).qcow2" }
         for i in 0..<1000 {
             let name = nameForId(i)
@@ -146,7 +146,7 @@ class UTMData: ObservableObject {
         return UUID().uuidString
     }
     
-    func newDefaultDriveName(for config: UTMConfiguration) -> String {
+    func newDefaultDriveName(for config: UTMQemuConfiguration) -> String {
         let nameForId = { (i: Int) in "drive\(i)" }
         for i in 0..<1000 {
             let name = nameForId(i)
@@ -208,11 +208,11 @@ class UTMData: ObservableObject {
         // discard cached drive selection
         selectedDiskImagesCache.removeAll()
         // delete orphaned drives
-        guard let orphanedDrives = vm.configuration.orphanedDrives else {
+        guard let orphanedDrives = vm.config.orphanedDrives else {
             return
         }
         for name in orphanedDrives {
-            let imagesPath = vm.configuration.imagesPath
+            let imagesPath = vm.config.imagesPath
             let orphanPath = imagesPath.appendingPathComponent(name)
             logger.debug("Removing orphaned drive '\(name)'")
             try fileManager.removeItem(at: orphanPath)
@@ -249,7 +249,7 @@ class UTMData: ObservableObject {
     }
     
     func clone(vm: UTMVirtualMachine) throws {
-        let newName = newDefaultVMName(base: vm.configuration.name)
+        let newName = newDefaultVMName(base: vm.config.name)
         let newPath = UTMVirtualMachine.virtualMachinePath(newName, inParentURL: documentsURL)
         
         try fileManager.copyItem(at: vm.path!, to: newPath)
@@ -272,7 +272,7 @@ class UTMData: ObservableObject {
     func edit(vm: UTMVirtualMachine) {
         DispatchQueue.main.async {
             // show orphans for proper removal
-            vm.configuration.recoverOrphanedDrives()
+            vm.config.recoverOrphanedDrives()
             self.selectedVM = vm
             self.showSettingsModal = true
             self.showNewVMSheet = false
@@ -300,12 +300,12 @@ class UTMData: ObservableObject {
     
     // MARK: - Export debug log
     
-    func exportDebugLog(forConfig: UTMConfiguration) throws -> [URL] {
+    func exportDebugLog(forConfig: UTMQemuConfiguration) throws -> [URL] {
         guard let path = forConfig.existingPath else {
             throw NSLocalizedString("No log found!", comment: "UTMData")
         }
-        let srcLogPath = path.appendingPathComponent(UTMConfiguration.debugLogName())
-        let dstLogPath = tempURL.appendingPathComponent(UTMConfiguration.debugLogName())
+        let srcLogPath = path.appendingPathComponent(UTMQemuConfiguration.debugLogName())
+        let dstLogPath = tempURL.appendingPathComponent(UTMQemuConfiguration.debugLogName())
         
         if fileManager.fileExists(atPath: dstLogPath.path) {
             try fileManager.removeItem(at: dstLogPath)
@@ -363,7 +363,7 @@ class UTMData: ObservableObject {
     
     // MARK: - Disk drive functions
     
-    func importDrive(_ drive: URL, for config: UTMConfiguration, imageType: UTMDiskImageType, on interface: String, copy: Bool) throws {
+    func importDrive(_ drive: URL, for config: UTMQemuConfiguration, imageType: UTMDiskImageType, on interface: String, copy: Bool) throws {
         _ = drive.startAccessingSecurityScopedResource()
         defer { drive.stopAccessingSecurityScopedResource() }
         
@@ -393,18 +393,18 @@ class UTMData: ObservableObject {
         }
     }
     
-    func importDrive(_ drive: URL, for config: UTMConfiguration, copy: Bool = true) throws {
+    func importDrive(_ drive: URL, for config: UTMQemuConfiguration, copy: Bool = true) throws {
         let imageType: UTMDiskImageType = drive.pathExtension.lowercased() == "iso" ? .CD : .disk
         let interface: String
         if let target = config.systemTarget {
-            interface = UTMConfiguration.defaultDriveInterface(forTarget: target, type: imageType)
+            interface = UTMQemuConfiguration.defaultDriveInterface(forTarget: target, type: imageType)
         } else {
             interface = "none"
         }
         try importDrive(drive, for: config, imageType: imageType, on: interface, copy: copy)
     }
     
-    func createDrive(_ drive: VMDriveImage, for config: UTMConfiguration, with driveImage: URL? = nil) throws {
+    func createDrive(_ drive: VMDriveImage, for config: UTMQemuConfiguration, with driveImage: URL? = nil) throws {
         var path: String = ""
         if !drive.removable {
             assert(driveImage == nil, "Cannot call createDrive with a driveImage!")
@@ -438,7 +438,7 @@ class UTMData: ObservableObject {
         }
     }
     
-    func removeDrive(at index: Int, for config: UTMConfiguration) throws {
+    func removeDrive(at index: Int, for config: UTMQemuConfiguration) throws {
         if let path = config.driveImagePath(for: index) {
             let fullPath = config.imagesPath.appendingPathComponent(path);
             if fileManager.fileExists(atPath: fullPath.path) {
@@ -479,7 +479,7 @@ class UTMData: ObservableObject {
     
     private func recreate(vm: UTMVirtualMachine) {
         guard let path = vm.path else {
-            logger.error("Attempting to refresh unsaved VM \(vm.configuration.name)")
+            logger.error("Attempting to refresh unsaved VM \(vm.config.name)")
             return
         }
         guard let newVM = UTMVirtualMachine(url: path) else {

+ 2 - 2
Platform/iOS/Display/VMDisplayMetalViewController+Gamepad.m

@@ -20,8 +20,8 @@
 #import "VMDisplayMetalViewController+Gamepad.h"
 #import "VMDisplayMetalViewController+Touch.h"
 #import "CSDisplayMetal.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
 #import "UTMLogging.h"
 
 const CGFloat kThumbstickSpeedMultiplier = 1000; // in points per second

+ 5 - 5
Platform/iOS/Display/VMDisplayMetalViewController+Pointer.m

@@ -14,16 +14,16 @@
 // limitations under the License.
 //
 
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
 #import "VMDisplayMetalViewController.h"
 #import "VMDisplayMetalViewController+Touch.h"
 #import "VMDisplayMetalViewController+Pointer.h"
 #import "VMCursor.h"
 #import "CSDisplayMetal.h"
 #import "VMScroll.h"
-#import "UTMVirtualMachine.h"
-#import "UTMVirtualMachine+SPICE.h"
+#import "UTMQemuVirtualMachine.h"
+#import "UTMQemuVirtualMachine+SPICE.h"
 #import "UTMLogging.h"
 
 @interface VMDisplayMetalViewController ()
@@ -108,7 +108,7 @@ NS_AVAILABLE_IOS(13.4)
 }
 
 - (BOOL)hasTouchpadPointer {
-    return !self.vmConfiguration.inputLegacy && !self.vmInput.serverModeCursor && self.indirectMouseType != VMMouseTypeRelative;
+    return !self.vmQemuConfig.inputLegacy && !self.vmInput.serverModeCursor && self.indirectMouseType != VMMouseTypeRelative;
 }
 
 - (UIPointerStyle *)pointerInteraction:(UIPointerInteraction *)interaction styleForRegion:(UIPointerRegion *)region {

+ 9 - 9
Platform/iOS/Display/VMDisplayMetalViewController+Touch.m

@@ -21,12 +21,12 @@
 #import "VMCursor.h"
 #import "VMScroll.h"
 #import "CSDisplayMetal.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Miscellaneous.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Miscellaneous.h"
 #import "UTMSpiceIO.h"
 #import "UTMLogging.h"
-#import "UTMVirtualMachine.h"
-#import "UTMVirtualMachine+SPICE.h"
+#import "UTMQemuVirtualMachine.h"
+#import "UTMQemuVirtualMachine+SPICE.h"
 
 const CGFloat kScrollSpeedReduction = 100.0f;
 const CGFloat kCursorResistance = 50.0f;
@@ -358,7 +358,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 
 - (CGPoint)moveMouseScroll:(CGPoint)translation {
     translation.y = CGPointToPixel(translation.y) / kScrollSpeedReduction;
-    if (self.vmConfiguration.inputScrollInvert) {
+    if (self.vmQemuConfig.inputScrollInvert) {
         translation.y = -translation.y;
     }
     [self.vmInput sendMouseScroll:kCSInputScrollSmooth button:self.mouseButtonDown dy:translation.y];
@@ -583,7 +583,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 #pragma mark - Touch event handling
 
 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
-    if (!_mouseCaptured && !self.vmConfiguration.inputLegacy) {
+    if (!_mouseCaptured && !self.vmQemuConfig.inputLegacy) {
         for (UITouch *touch in [event touchesForView:self.mtkView]) {
             if (@available(iOS 14, *)) {
                 if (self.prefersPointerLocked && (touch.type == UITouchTypeIndirect || touch.type == UITouchTypeIndirectPointer)) {
@@ -626,7 +626,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 
 - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     // move cursor in client mode, in server mode we handle in gesturePan
-    if (!self.vmConfiguration.inputLegacy && !self.vmInput.serverModeCursor) {
+    if (!self.vmQemuConfig.inputLegacy && !self.vmInput.serverModeCursor) {
         for (UITouch *touch in [event touchesForView:self.mtkView]) {
             [_cursor updateMovement:[touch locationInView:self.mtkView]];
             break; // handle single touch
@@ -637,7 +637,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 
 - (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     // release click in client mode, in server mode we handle in gesturePan
-    if (!self.vmConfiguration.inputLegacy && !self.vmInput.serverModeCursor) {
+    if (!self.vmQemuConfig.inputLegacy && !self.vmInput.serverModeCursor) {
         [self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES];
     }
     [super touchesCancelled:touches withEvent:event];
@@ -645,7 +645,7 @@ static CGFloat CGPointToPixel(CGFloat point) {
 
 - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
     // release click in client mode, in server mode we handle in gesturePan
-    if (!self.vmConfiguration.inputLegacy && !self.vmInput.serverModeCursor) {
+    if (!self.vmQemuConfig.inputLegacy && !self.vmInput.serverModeCursor) {
         [self dragCursor:UIGestureRecognizerStateEnded primary:YES secondary:YES middle:YES];
     }
     [super touchesEnded:touches withEvent:event];

+ 7 - 7
Platform/iOS/Display/VMDisplayMetalViewController.m

@@ -25,8 +25,8 @@
 #import "UTMRenderer.h"
 #import "UTMVirtualMachine.h"
 #import "UTMQemuManager.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Display.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Display.h"
 #import "UTMLogging.h"
 #import "CSDisplayMetal.h"
 #import "UTMScreenshot.h"
@@ -75,8 +75,8 @@
     // Initialize our renderer with the view size
     [_renderer mtkView:self.mtkView drawableSizeWillChange:self.mtkView.drawableSize];
     
-    [_renderer changeUpscaler:self.vmConfiguration.displayUpscalerValue
-                   downscaler:self.vmConfiguration.displayDownscalerValue];
+    [_renderer changeUpscaler:self.vmQemuConfig.displayUpscalerValue
+                   downscaler:self.vmQemuConfig.displayDownscalerValue];
     
     self.mtkView.delegate = _renderer;
     
@@ -125,7 +125,7 @@
             self.placeholderImageView.image = self.vm.screenshot.image;
             self.mtkView.hidden = YES;
         } completion:nil];
-        if (self.vmConfiguration.shareClipboardEnabled) {
+        if (self.vmQemuConfig.shareClipboardEnabled) {
             [[UTMPasteboard generalPasteboard] releasePollingModeForObject:self];
         }
 #if !defined(WITH_QEMU_TCI)
@@ -143,7 +143,7 @@
         self.mtkView.hidden = NO;
     } completion:nil];
     [self displayResize:self.view.bounds.size];
-    if (self.vmConfiguration.shareClipboardEnabled) {
+    if (self.vmQemuConfig.shareClipboardEnabled) {
         [[UTMPasteboard generalPasteboard] requestPollingModeForObject:self];
     }
 }
@@ -188,7 +188,7 @@
 - (void)displayResize:(CGSize)size {
     UTMLog(@"resizing to (%f, %f)", size.width, size.height);
     CGRect bounds = CGRectMake(0, 0, size.width, size.height);
-    if (self.vmConfiguration.displayRetina) {
+    if (self.vmQemuConfig.displayRetina) {
         CGFloat scale = [UIScreen mainScreen].scale;
         CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale);
         bounds = CGRectApplyAffineTransform(bounds, transform);

+ 1 - 1
Platform/iOS/Display/VMDisplayTerminalViewController+Keyboard.m

@@ -14,7 +14,7 @@
 // limitations under the License.
 //
 
-#import "UTMVirtualMachine+Terminal.h"
+#import "UTMQemuVirtualMachine+Terminal.h"
 #import "UTMLogging.h"
 #import "VMDisplayTerminalViewController+Keyboard.h"
 #import "VMKeyboardButton.h"

+ 7 - 7
Platform/iOS/Display/VMDisplayTerminalViewController.m

@@ -16,11 +16,11 @@
 
 #import "VMDisplayTerminalViewController.h"
 #import "VMDisplayTerminalViewController+Keyboard.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Display.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Display.h"
 #import "UTMLogging.h"
-#import "UTMVirtualMachine.h"
-#import "UTMVirtualMachine+Terminal.h"
+#import "UTMQemuVirtualMachine.h"
+#import "UTMQemuVirtualMachine+Terminal.h"
 #import "UIViewController+Extensions.h"
 #import "WKWebView+Workarounds.h"
 #import "UTM-Swift.h"
@@ -94,10 +94,10 @@ NSString* const kVMSendTerminalSizeHandler = @"UTMSendTerminalSize";
 }
 
 - (void)updateSettings {
-    [_webView evaluateJavaScript:[NSString stringWithFormat:@"changeFont('%@', %ld);", self.vmConfiguration.consoleFont, self.vmConfiguration.consoleFontSize.integerValue] completionHandler:^(id _Nullable _, NSError * _Nullable error) {
+    [_webView evaluateJavaScript:[NSString stringWithFormat:@"changeFont('%@', %ld);", self.vmQemuConfig.consoleFont, self.vmQemuConfig.consoleFontSize.integerValue] completionHandler:^(id _Nullable _, NSError * _Nullable error) {
         UTMLog(@"changeFont error: %@", error);
     }];
-    [_webView evaluateJavaScript:[NSString stringWithFormat:@"setCursorBlink(%@);", self.vmConfiguration.consoleCursorBlink ? @"true" : @"false"] completionHandler:^(id _Nullable _, NSError * _Nullable error) {
+    [_webView evaluateJavaScript:[NSString stringWithFormat:@"setCursorBlink(%@);", self.vmQemuConfig.consoleCursorBlink ? @"true" : @"false"] completionHandler:^(id _Nullable _, NSError * _Nullable error) {
         UTMLog(@"setCursorBlink error: %@", error);
     }];
 }
@@ -131,7 +131,7 @@ NSString* const kVMSendTerminalSizeHandler = @"UTMSendTerminalSize";
 #pragma mark - Resize console
 
 - (void)changeDisplayZoom:(UIButton *)sender {
-    NSString *cmd = self.vmConfiguration.consoleResizeCommand;
+    NSString *cmd = self.vmQemuConfig.consoleResizeCommand;
     if (cmd.length == 0) {
         cmd = kVMDefaultResizeCmd;
     }

+ 4 - 2
Platform/iOS/Display/VMDisplayViewController.h

@@ -18,7 +18,8 @@
 #import "CSInput.h"
 #import "UTMVirtualMachineDelegate.h"
 
-@class UTMVirtualMachine;
+@class UTMQemuConfiguration;
+@class UTMQemuVirtualMachine;
 @class VMKeyboardButton;
 @class VMRemovableDrivesViewController;
 @class VMToolbarActions;
@@ -41,6 +42,7 @@
 @property (weak, nonatomic) IBOutlet UIButton *resumeBigButton;
 @property (strong, nonatomic) IBOutletCollection(VMKeyboardButton) NSArray *customKeyModifierButtons;
 
+@property (nonatomic, readonly) UTMQemuConfiguration *vmQemuConfig;
 @property (nonatomic) VMToolbarActions *toolbar;
 @property (nonatomic) UIViewController *floatingToolbarViewController;
 @property (nonatomic) VMRemovableDrivesViewController *removableDrivesViewController;
@@ -48,7 +50,7 @@
 
 @property (nonatomic) BOOL hasAutoSave;
 @property (nonatomic, readwrite) BOOL prefersStatusBarHidden;
-@property (nonatomic, strong) UTMVirtualMachine *vm;
+@property (nonatomic, strong) UTMQemuVirtualMachine *vm;
 
 @property (nonatomic, strong) NSMutableArray<UIKeyCommand *> *mutableKeyCommands;
 

+ 4 - 0
Platform/iOS/Display/VMDisplayViewController.m

@@ -27,6 +27,10 @@
 @synthesize keyboardVisible = _keyboardVisible;
 @synthesize toolbarVisible = _toolbarVisible;
 
+- (UTMQemuConfiguration *)vmQemuConfig {
+    return (UTMQemuConfiguration *)vmConfiguration;
+}
+
 - (BOOL)prefersHomeIndicatorAutoHidden {
     return YES; // always hide home indicator
 }

+ 1 - 1
Platform/iOS/Legacy/VMConfigCreateViewController.m

@@ -15,7 +15,7 @@
 //
 
 #import "VMConfigCreateViewController.h"
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 #import "VMConfigTextField.h"
 
 @interface VMConfigCreateViewController ()

+ 3 - 3
Platform/iOS/Legacy/VMConfigDisplayViewController.m

@@ -15,9 +15,9 @@
 //
 
 #import "VMConfigDisplayViewController.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Display.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Display.h"
 
 @interface VMConfigDisplayViewController ()
 

+ 1 - 1
Platform/iOS/Legacy/VMConfigDriveDetailViewController.h

@@ -15,7 +15,7 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "UTMConfiguration+Drives.h"
+#import "UTMQemuConfiguration+Drives.h"
 #import "VMConfigViewController.h"
 
 @class VMConfigTogglePickerCell;

+ 10 - 10
Platform/iOS/Legacy/VMConfigDriveDetailViewController.m

@@ -15,10 +15,10 @@
 //
 
 #import "VMConfigDriveDetailViewController.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+Defaults.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+Defaults.h"
+#import "UTMQemuConfiguration+System.h"
 #import "VMConfigDrivePickerViewController.h"
 #import "VMConfigPickerView.h"
 #import "VMConfigTogglePickerCell.h"
@@ -49,7 +49,7 @@
         [self showImagePathCell:!self.removable animated:NO];
     } else {
         self.imageType = UTMDiskImageTypeDisk;
-        self.driveInterfaceType = [UTMConfiguration defaultDriveInterfaceForTarget:self.configuration.systemTarget type:UTMDiskImageTypeDisk];
+        self.driveInterfaceType = [UTMQemuConfiguration defaultDriveInterfaceForTarget:self.configuration.systemTarget type:UTMDiskImageTypeDisk];
     }
     if (self.imageType == UTMDiskImageTypeDisk || self.imageType == UTMDiskImageTypeCD) {
         [self showDriveTypeOptions:YES animated:NO];
@@ -76,7 +76,7 @@
     if (self.existing) {
         [self.configuration setDriveImageType:imageType forIndex:self.driveIndex];
     }
-    self.imageTypePickerCell.detailTextLabel.text = [UTMConfiguration supportedImageTypes][imageType];
+    self.imageTypePickerCell.detailTextLabel.text = [UTMQemuConfiguration supportedImageTypes][imageType];
 }
 
 - (void)setDriveInterfaceType:(NSString *)driveInterfaceType {
@@ -104,7 +104,7 @@
 - (void)imageTypeChanged {
     if (self.imageType == UTMDiskImageTypeDisk || self.imageType == UTMDiskImageTypeCD) {
         if (self.driveInterfaceType.length == 0) {
-            self.driveInterfaceType = [UTMConfiguration defaultDriveInterfaceForTarget:self.configuration.systemTarget type:self.imageType];
+            self.driveInterfaceType = [UTMQemuConfiguration defaultDriveInterfaceForTarget:self.configuration.systemTarget type:self.imageType];
         }
         [self showDriveTypeOptions:YES animated:NO];
     } else {
@@ -116,7 +116,7 @@
 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
     NSAssert(component == 0, @"Invalid component");
     if (pickerView == self.driveLocationPickerCell.picker) {
-        self.driveInterfaceType = [UTMConfiguration supportedDriveInterfaces][row];
+        self.driveInterfaceType = [UTMQemuConfiguration supportedDriveInterfaces][row];
     } else if (pickerView == self.imageTypePickerCell.picker) {
         self.imageType = row;
         [self imageTypeChanged];
@@ -130,8 +130,8 @@
 // In a storyboard-based application, you will often want to do a little preparation before navigation
 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
     if ([segue.identifier isEqualToString:@"selectDiskSegue"]) {
-        NSAssert([segue.destinationViewController conformsToProtocol:@protocol(UTMConfigurationDelegate)], @"Invalid segue destination");
-        id<UTMConfigurationDelegate> controller = (id<UTMConfigurationDelegate>)segue.destinationViewController;
+        NSAssert([segue.destinationViewController conformsToProtocol:@protocol(UTMQemuConfigurationDelegate)], @"Invalid segue destination");
+        id<UTMQemuConfigurationDelegate> controller = (id<UTMQemuConfigurationDelegate>)segue.destinationViewController;
         controller.configuration = self.configuration;
     }
 }

+ 2 - 2
Platform/iOS/Legacy/VMConfigDrivePickerViewController.h

@@ -15,11 +15,11 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "UTMConfigurationDelegate.h"
+#import "UTMQemuConfigurationDelegate.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface VMConfigDrivePickerViewController : UITableViewController<UTMConfigurationDelegate, UIDocumentPickerDelegate>
+@interface VMConfigDrivePickerViewController : UITableViewController<UTMQemuConfigurationDelegate, UIDocumentPickerDelegate>
 
 @property (nonatomic, strong) NSURL *imagesPath;
 @property (weak, nonatomic) IBOutlet UIBarButtonItem *addButtonItem;

+ 4 - 4
Platform/iOS/Legacy/VMConfigDrivePickerViewController.m

@@ -15,8 +15,8 @@
 //
 
 #import "VMConfigDrivePickerViewController.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
 #import "VMConfigDriveCreateViewController.h"
 
 @interface VMConfigDrivePickerViewController ()
@@ -42,9 +42,9 @@
 
 - (void)refreshViewFromConfiguration {
     if (self.configuration.existingPath) {
-        self.imagesPath = [self.configuration.existingPath URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
+        self.imagesPath = [self.configuration.existingPath URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
     } else {
-        self.imagesPath = [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
+        self.imagesPath = [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
     }
 }
 

+ 2 - 2
Platform/iOS/Legacy/VMConfigDrivesViewController.h

@@ -15,11 +15,11 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "UTMConfigurationDelegate.h"
+#import "UTMQemuConfigurationDelegate.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface VMConfigDrivesViewController : UITableViewController<UTMConfigurationDelegate>
+@interface VMConfigDrivesViewController : UITableViewController<UTMQemuConfigurationDelegate>
 
 @property (weak, nonatomic) IBOutlet UIBarButtonItem *addButtonItem;
 

+ 5 - 5
Platform/iOS/Legacy/VMConfigDrivesViewController.m

@@ -16,8 +16,8 @@
 
 #import "VMConfigDrivesViewController.h"
 #import "VMConfigDriveDetailViewController.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
 
 @interface VMConfigDrivesViewController ()
 
@@ -63,7 +63,7 @@
         cell.textLabel.text = [self.configuration driveImagePathForIndex:indexPath.row];
     }
     UTMDiskImageType type = [self.configuration driveImageTypeForIndex:indexPath.row];
-    NSString *typeStr = [UTMConfiguration supportedImageTypesPretty][type];
+    NSString *typeStr = [UTMQemuConfiguration supportedImageTypesPretty][type];
     NSString *interface = [self.configuration driveInterfaceTypeForIndex:indexPath.row];
     cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@", typeStr, interface];
     
@@ -132,9 +132,9 @@
 - (void)promptDelete:(NSString *)name {
     NSURL *path;
     if (self.configuration.existingPath) {
-        path = [self.configuration.existingPath URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
+        path = [self.configuration.existingPath URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
     } else {
-        path = [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMConfiguration diskImagesDirectory] isDirectory:YES];
+        path = [[NSFileManager defaultManager].temporaryDirectory URLByAppendingPathComponent:[UTMQemuConfiguration diskImagesDirectory] isDirectory:YES];
     }
     UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Delete Data", @"VMConfigDrivesViewController") message:NSLocalizedString(@"Do you want to also delete the disk image data? If yes, the data will be lost. Otherwise, you can create a new drive with the existing data.", @"VMConfigDrivesViewController") preferredStyle:UIAlertControllerStyleAlert];
     UIAlertAction *delete = [UIAlertAction actionWithTitle:NSLocalizedString(@"Yes", @"Yes button") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){

+ 4 - 4
Platform/iOS/Legacy/VMConfigExistingViewController.m

@@ -16,8 +16,8 @@
 
 #import "VMConfigExistingViewController.h"
 #import "UIViewController+Extensions.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
 #import "VMConfigTextField.h"
 
 @interface VMConfigExistingViewController ()
@@ -68,13 +68,13 @@
 - (void)exportLog {
     NSURL *path;
     if (self.configuration.existingPath) {
-        path = [self.configuration.existingPath URLByAppendingPathComponent:[UTMConfiguration debugLogName]];
+        path = [self.configuration.existingPath URLByAppendingPathComponent:[UTMQemuConfiguration debugLogName]];
     }
     if (![[NSFileManager defaultManager] fileExistsAtPath:path.path]) {
         [self showAlert:NSLocalizedString(@"No debug log found!", @"VMConfigExistingViewController") actions:nil completion:nil];
     } else {
         NSError *err;
-        NSURL *temp = [NSURL fileURLWithPathComponents:@[NSTemporaryDirectory(), [UTMConfiguration debugLogName]]];
+        NSURL *temp = [NSURL fileURLWithPathComponents:@[NSTemporaryDirectory(), [UTMQemuConfiguration debugLogName]]];
         [[NSFileManager defaultManager] removeItemAtURL:temp error:nil];
         if ([[NSFileManager defaultManager] copyItemAtURL:path toURL:temp error:&err]) {
             UIActivityViewController *activityViewController = [[UIActivityViewController alloc] initWithActivityItems:@[temp] applicationActivities:nil];

+ 1 - 1
Platform/iOS/Legacy/VMConfigInputViewController.m

@@ -15,7 +15,7 @@
 //
 
 #import "VMConfigInputViewController.h"
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 
 @interface VMConfigInputViewController ()
 

+ 2 - 2
Platform/iOS/Legacy/VMConfigPortForwardingViewController.h

@@ -15,11 +15,11 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "UTMConfigurationDelegate.h"
+#import "UTMQemuConfigurationDelegate.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface VMConfigPortForwardingViewController : UITableViewController<UTMConfigurationDelegate>
+@interface VMConfigPortForwardingViewController : UITableViewController<UTMQemuConfigurationDelegate>
 
 @property (weak, nonatomic) IBOutlet UIBarButtonItem *addButtonItem;
 

+ 6 - 6
Platform/iOS/Legacy/VMConfigPortForwardingViewController.m

@@ -15,8 +15,8 @@
 //
 
 #import "VMConfigPortForwardingViewController.h"
-#import "UTMConfiguration+Networking.h"
-#import "UTMConfigurationPortForward.h"
+#import "UTMQemuConfiguration+Networking.h"
+#import "UTMQemuConfigurationPortForward.h"
 
 @interface VMConfigPortForwardingViewController ()
 
@@ -54,7 +54,7 @@
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     NSAssert(indexPath.section == 0, @"Invalid section");
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"portForwardCell" forIndexPath:indexPath];
-    UTMConfigurationPortForward *portForward = [self.configuration portForwardForIndex:indexPath.row];
+    UTMQemuConfigurationPortForward *portForward = [self.configuration portForwardForIndex:indexPath.row];
     cell.textLabel.text = [NSString stringWithFormat:@"%@:%@ ➡️ %@:%@", portForward.hostAddress, portForward.hostPort, portForward.guestAddress, portForward.guestPort];
     cell.detailTextLabel.text = portForward.protocol;
     return cell;
@@ -71,7 +71,7 @@
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     NSAssert(indexPath.section == 0, @"Invalid section");
-    UTMConfigurationPortForward *portForward = [self.configuration portForwardForIndex:indexPath.row];
+    UTMQemuConfigurationPortForward *portForward = [self.configuration portForwardForIndex:indexPath.row];
     if (portForward) {
         [self createPortForwardFormTCP:[portForward.protocol isEqualToString:@"tcp"]
                               existing:portForward
@@ -86,7 +86,7 @@
     [self createPortForwardFormTCP:tcp existing:nil atIndex:0];
 }
 
-- (void)createPortForwardFormTCP:(BOOL)tcp existing:(nullable UTMConfigurationPortForward *)existing atIndex:(NSUInteger)index {
+- (void)createPortForwardFormTCP:(BOOL)tcp existing:(nullable UTMQemuConfigurationPortForward *)existing atIndex:(NSUInteger)index {
     UIAlertController *alertController = [UIAlertController alertControllerWithTitle:tcp ? NSLocalizedString(@"TCP Forward", @"VMConfigPortForwardingViewController") : NSLocalizedString(@"UDP Forward", @"VMConfigPortForwardingViewController")
                                                                              message:nil
                                                                       preferredStyle:UIAlertControllerStyleAlert];
@@ -116,7 +116,7 @@
     [alertController addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Done", @"VMConfigPortForwardingViewController")
                                                         style:UIAlertActionStyleDefault
                                                       handler:^(UIAlertAction * _Nonnull action) {
-        UTMConfigurationPortForward *portForward = [[UTMConfigurationPortForward alloc] init];
+        UTMQemuConfigurationPortForward *portForward = [[UTMQemuConfigurationPortForward alloc] init];
         portForward.protocol = tcp ? @"tcp" : @"udp";
         portForward.hostAddress = alertController.textFields[0].text;
         portForward.hostPort = @([alertController.textFields[1].text integerValue]);

+ 2 - 2
Platform/iOS/Legacy/VMConfigSharingViewController.m

@@ -17,8 +17,8 @@
 #import <MobileCoreServices/MobileCoreServices.h>
 #import "UIViewController+Extensions.h"
 #import "VMConfigSharingViewController.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Sharing.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Sharing.h"
 #import "UTMLogging.h"
 #import "VMConfigSwitch.h"
 #import "VMConfigDirectoryPickerViewController.h"

+ 1 - 1
Platform/iOS/Legacy/VMConfigSoundViewController.m

@@ -15,7 +15,7 @@
 //
 
 #import "VMConfigSoundViewController.h"
-#import "UTMConfiguration.h"
+#import "UTMQemuConfiguration.h"
 #import "VMConfigSwitch.h"
 
 @interface VMConfigSoundViewController ()

+ 3 - 3
Platform/iOS/Legacy/VMConfigSystemArgumentsViewController.h

@@ -15,18 +15,18 @@
 //
 
 #import <UIKit/UIKit.h>
-#import "UTMConfigurationDelegate.h"
+#import "UTMQemuConfigurationDelegate.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface VMConfigSystemArgumentsViewController : UITableViewController<UTMConfigurationDelegate>
+@interface VMConfigSystemArgumentsViewController : UITableViewController<UTMQemuConfigurationDelegate>
 
 @property (weak, nonatomic) IBOutlet UIBarButtonItem *addButtonItem;
 @property (strong, nonatomic) IBOutlet UITableView *argTableView;
 
 @end
 
-@interface VMConfigSystemArgumentsTextCell : UITableViewCell<UTMConfigurationDelegate>
+@interface VMConfigSystemArgumentsTextCell : UITableViewCell<UTMQemuConfigurationDelegate>
 @property (weak, nonatomic) IBOutlet UITextField *argTextItem;
 
 @end

+ 1 - 1
Platform/iOS/Legacy/VMConfigSystemArgumentsViewController.m

@@ -16,7 +16,7 @@
 
 #import "VMConfigSystemArgumentsViewController.h"
 #import "VMConfigDriveDetailViewController.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration+System.h"
 
 @interface VMConfigSystemArgumentsViewController ()
 

+ 7 - 7
Platform/iOS/Legacy/VMConfigSystemViewController.m

@@ -19,8 +19,8 @@
 #import <sys/sysctl.h>
 #import "UIViewController+Extensions.h"
 #import "VMConfigSystemViewController.h"
-#import "UTMConfiguration+Constants.h"
-#import "UTMConfiguration+System.h"
+#import "UTMQemuConfiguration+Constants.h"
+#import "UTMQemuConfiguration+System.h"
 #import "UTMJailbreak.h"
 #import "UTMLogging.h"
 #import "VMConfigPickerView.h"
@@ -66,7 +66,7 @@ const float kMemoryWarningThreshold = 0.8;
 
 - (void)pickerCell:(VMConfigTogglePickerCell *)cell showPicker:(BOOL)visible animated:(BOOL)animated {
     if (visible && cell.picker == self.targetPicker) {
-        NSUInteger index = [[UTMConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture] indexOfObject:cell.detailTextLabel.text];
+        NSUInteger index = [[UTMQemuConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture] indexOfObject:cell.detailTextLabel.text];
         if (index != NSNotFound) {
             [cell.picker selectRow:index inComponent:0 animated:NO];
         }
@@ -77,7 +77,7 @@ const float kMemoryWarningThreshold = 0.8;
 - (NSInteger)pickerView:(nonnull UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
     NSAssert(component == 0, @"Invalid component");
     if (pickerView == self.targetPicker) {
-        return [UTMConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture].count;
+        return [UTMQemuConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture].count;
     } else {
         return [super pickerView:pickerView numberOfRowsInComponent:component];
     }
@@ -86,7 +86,7 @@ const float kMemoryWarningThreshold = 0.8;
 - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
     NSAssert(component == 0, @"Invalid component");
     if (pickerView == self.targetPicker) {
-        return [UTMConfiguration supportedTargetsForArchitecturePretty:self.configuration.systemArchitecture][row];
+        return [UTMQemuConfiguration supportedTargetsForArchitecturePretty:self.configuration.systemArchitecture][row];
     } else {
         return [super pickerView:pickerView titleForRow:row forComponent:component];
     }
@@ -99,7 +99,7 @@ const float kMemoryWarningThreshold = 0.8;
         [super pickerView:pickerView didSelectRow:row inComponent:component];
         // refresh system picker with default target
         if (![prev isEqualToString:self.configuration.systemArchitecture]) {
-            NSInteger index = [UTMConfiguration defaultTargetIndexForArchitecture:self.configuration.systemArchitecture];
+            NSInteger index = [UTMQemuConfiguration defaultTargetIndexForArchitecture:self.configuration.systemArchitecture];
             [self.targetPicker reloadAllComponents];
             [self.targetPicker selectRow:index inComponent:0 animated:YES];
             [self pickerView:self.targetPicker didSelectRow:index inComponent:0];
@@ -107,7 +107,7 @@ const float kMemoryWarningThreshold = 0.8;
     } else if (pickerView == self.targetPicker) {
         NSAssert([pickerView isKindOfClass:[VMConfigPickerView class]], @"Invalid picker");
         VMConfigPickerView *vmPicker = (VMConfigPickerView *)pickerView;
-        NSString *selected = [UTMConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture][row];
+        NSString *selected = [UTMQemuConfiguration supportedTargetsForArchitecture:self.configuration.systemArchitecture][row];
         [self.configuration setValue:selected forKey:vmPicker.selectedOptionCell.configurationPath];
         vmPicker.selectedOptionCell.detailTextLabel.text = selected;
     } else {

+ 2 - 2
Platform/iOS/Legacy/VMConfigViewController.h

@@ -16,7 +16,7 @@
 
 #import <UIKit/UIKit.h>
 #import "StaticDataTableViewController.h"
-#import "UTMConfigurationDelegate.h"
+#import "UTMQemuConfigurationDelegate.h"
 
 @class VMConfigTextField;
 @class VMConfigStepper;
@@ -25,7 +25,7 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface VMConfigViewController : StaticDataTableViewController<UTMConfigurationDelegate, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource>
+@interface VMConfigViewController : StaticDataTableViewController<UTMQemuConfigurationDelegate, UITextFieldDelegate, UIPickerViewDelegate, UIPickerViewDataSource>
 
 @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *configControls;
 @property (nonatomic, strong) IBOutletCollection(VMConfigTogglePickerCell) NSArray *configPickerToggles;

+ 8 - 8
Platform/iOS/Legacy/VMConfigViewController.m

@@ -21,8 +21,8 @@
 #import "VMConfigSwitch.h"
 #import "VMConfigTextField.h"
 #import "VMConfigTogglePickerCell.h"
-#import "UTMConfiguration.h"
-#import "UTMConfiguration+Constants.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuConfiguration+Constants.h"
 #import "UTMLogging.h"
 
 void *kVMConfigViewControllerContext = &kVMConfigViewControllerContext;
@@ -52,8 +52,8 @@ void *kVMConfigViewControllerContext = &kVMConfigViewControllerContext;
 #pragma mark - Navigation
 
 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
-    if ([segue.destinationViewController conformsToProtocol:@protocol(UTMConfigurationDelegate)]) {
-        id<UTMConfigurationDelegate> dst = (id<UTMConfigurationDelegate>)segue.destinationViewController;
+    if ([segue.destinationViewController conformsToProtocol:@protocol(UTMQemuConfigurationDelegate)]) {
+        id<UTMQemuConfigurationDelegate> dst = (id<UTMQemuConfigurationDelegate>)segue.destinationViewController;
         dst.configuration = self.configuration;
     }
 }
@@ -105,7 +105,7 @@ void *kVMConfigViewControllerContext = &kVMConfigViewControllerContext;
 
 - (void)pickerCell:(VMConfigTogglePickerCell *)cell showPicker:(BOOL)visible animated:(BOOL)animated {
     if (visible) {
-        NSUInteger index = [[UTMConfiguration supportedOptions:cell.picker.supportedOptionsPath pretty:NO] indexOfObject:cell.detailTextLabel.text];
+        NSUInteger index = [[UTMQemuConfiguration supportedOptions:cell.picker.supportedOptionsPath pretty:NO] indexOfObject:cell.detailTextLabel.text];
         if (index != NSNotFound) {
             [cell.picker selectRow:index inComponent:0 animated:NO];
         }
@@ -133,21 +133,21 @@ void *kVMConfigViewControllerContext = &kVMConfigViewControllerContext;
     NSAssert(component == 0, @"Invalid component");
     NSAssert([pickerView isKindOfClass:[VMConfigPickerView class]], @"Invalid picker");
     VMConfigPickerView *vmPicker = (VMConfigPickerView *)pickerView;
-    return [UTMConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:NO].count;
+    return [UTMQemuConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:NO].count;
 }
 
 - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
     NSAssert(component == 0, @"Invalid component");
     NSAssert([pickerView isKindOfClass:[VMConfigPickerView class]], @"Invalid picker");
     VMConfigPickerView *vmPicker = (VMConfigPickerView *)pickerView;
-    return [UTMConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:YES][row];
+    return [UTMQemuConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:YES][row];
 }
 
 - (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
     NSAssert(component == 0, @"Invalid component");
     NSAssert([pickerView isKindOfClass:[VMConfigPickerView class]], @"Invalid picker");
     VMConfigPickerView *vmPicker = (VMConfigPickerView *)pickerView;
-    NSString *selected = [UTMConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:NO][row];
+    NSString *selected = [UTMQemuConfiguration supportedOptions:vmPicker.supportedOptionsPath pretty:NO][row];
     [self.configuration setValue:selected forKey:vmPicker.selectedOptionCell.configurationPath];
     vmPicker.selectedOptionCell.detailTextLabel.text = selected;
 }

+ 34 - 34
Platform/iOS/Legacy/VMListViewController.m

@@ -17,9 +17,9 @@
 #import "VMListViewController.h"
 #import "AppDelegate.h"
 #import "VMListViewCell.h"
-#import "UTMConfigurationDelegate.h"
-#import "UTMConfiguration.h"
-#import "UTMVirtualMachine.h"
+#import "UTMQemuConfigurationDelegate.h"
+#import "UTMQemuConfiguration.h"
+#import "UTMQemuVirtualMachine.h"
 #import "UIViewController+Extensions.h"
 #import "VMDisplayMetalViewController.h"
 #import "VMDisplayTerminalViewController.h"
@@ -31,8 +31,8 @@
 @interface VMListViewController ()
 
 @property (nonatomic, readonly) NSURL *documentsPath;
-@property (nonatomic, strong) UTMVirtualMachine *modifyingVM;
-@property (nonatomic, strong) NSArray<UTMVirtualMachine *> *vmList;
+@property (nonatomic, strong) UTMQemuVirtualMachine *modifyingVM;
+@property (nonatomic, strong) NSArray<UTMQemuVirtualMachine *> *vmList;
 @property (nonatomic, nullable, strong) UIAlertController *alert;
 @property (nonatomic, strong) dispatch_semaphore_t viewVisibleSema;
 @property (nonatomic, strong) dispatch_queue_t viewVisibleQueue;
@@ -120,14 +120,14 @@
     });
 }
 
-- (NSArray<UTMVirtualMachine *> *)fetchVirtualMachines {
+- (NSArray<UTMQemuVirtualMachine *> *)fetchVirtualMachines {
     NSArray<NSURL *> *files = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:self.documentsPath includingPropertiesForKeys:@[NSURLIsDirectoryKey] options:NSDirectoryEnumerationSkipsHiddenFiles error:nil];
-    NSMutableArray<UTMVirtualMachine *> *vms = [[NSMutableArray alloc] initWithCapacity:files.count];
+    NSMutableArray<UTMQemuVirtualMachine *> *vms = [[NSMutableArray alloc] initWithCapacity:files.count];
     for (NSURL *file in files) {
         NSNumber *isDir;
         [file getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:nil];
-        if ([isDir boolValue] && [UTMVirtualMachine URLisVirtualMachine:file]) {
-            UTMVirtualMachine *vm = [[UTMVirtualMachine alloc] initWithURL:file];
+        if ([isDir boolValue] && [UTMQemuVirtualMachine URLisVirtualMachine:file]) {
+            UTMQemuVirtualMachine *vm = (UTMQemuVirtualMachine *)[UTMQemuVirtualMachine virtualMachineWithURL:file];
             if (vm) {
                 [vms addObject:vm];
             }
@@ -143,7 +143,7 @@
     NSUInteger idx = 1;
     do {
         NSString *name = nameForId(idx);
-        NSURL *file = [UTMVirtualMachine virtualMachinePath:name inParentURL:self.documentsPath];
+        NSURL *file = [UTMQemuVirtualMachine virtualMachinePath:name inParentURL:self.documentsPath];
         if (![[NSFileManager defaultManager] fileExistsAtPath:file.path]) {
             return name;
         }
@@ -188,7 +188,7 @@
                                                             preferredStyle:UIAlertControllerStyleAlert];
     [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"OK button") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
         NSString *name = alert.textFields[0].text;
-        NSURL *newPath = [UTMVirtualMachine virtualMachinePath:name inParentURL:self.documentsPath];
+        NSURL *newPath = [UTMQemuVirtualMachine virtualMachinePath:name inParentURL:self.documentsPath];
         dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
             NSError *err = nil;
             [self workStartedWhenVisible:[NSString stringWithFormat:NSLocalizedString(@"Saving %@...", @"Save VM overlay"), name]];
@@ -199,7 +199,7 @@
     }]];
     [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"Cancel button") style:UIAlertActionStyleCancel handler:nil]];
     [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
-        textField.text = [UTMVirtualMachine virtualMachineName:url];
+        textField.text = [UTMQemuVirtualMachine virtualMachineName:url];
     }];
     [self presentViewController:alert animated:YES completion:nil];
 }
@@ -227,7 +227,7 @@
 }
 
 - (void)deleteVM:(NSURL *)url {
-    NSString *name = [UTMVirtualMachine virtualMachineName:url];
+    NSString *name = [UTMQemuVirtualMachine virtualMachineName:url];
     [self showAlertSerialized:NSLocalizedString(@"Are you sure you want to delete this VM? Any drives associated will also be deleted.", @"Delete confirmation") isQuestion:YES completion:^{
         NSError *err = nil;
         [self workStartedWhenVisible:[NSString stringWithFormat:NSLocalizedString(@"Deleting %@...", @"Delete VM overlay"), name]];
@@ -239,7 +239,7 @@
 
 #pragma mark - Navigation
 
-- (nonnull UTMVirtualMachine *)vmForCell:(id)cell {
+- (nonnull UTMQemuVirtualMachine *)vmForCell:(id)cell {
     NSIndexPath *index = [self.collectionView indexPathForCell:cell];
     NSAssert(index, @"Cannot find index for selected VM");
     NSAssert(index.section == 0, @"Invalid section");
@@ -251,30 +251,30 @@
     if ([segue.identifier isEqualToString:@"editVMConfig"]){
         NSAssert([segue.destinationViewController isKindOfClass:[UINavigationController class]], @"Destination not a navigation view");
         UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
-        NSAssert([navController.topViewController conformsToProtocol:@protocol(UTMConfigurationDelegate)], @"Invalid segue destination");
-        id<UTMConfigurationDelegate> controller = (id<UTMConfigurationDelegate>)navController.topViewController;
+        NSAssert([navController.topViewController conformsToProtocol:@protocol(UTMQemuConfigurationDelegate)], @"Invalid segue destination");
+        id<UTMQemuConfigurationDelegate> controller = (id<UTMQemuConfigurationDelegate>)navController.topViewController;
         NSAssert([sender isKindOfClass:[UIButton class]], @"Sender is not a UIButton");
         id cell = ((UIButton *)sender).superview.superview;
         self.modifyingVM = [self vmForCell:cell];
-        controller.configuration = self.modifyingVM.configuration;
+        controller.configuration = self.modifyingVM.qemuConfig;
     } else if ([segue.identifier isEqualToString:@"newVM"]) {
         NSAssert([segue.destinationViewController isKindOfClass:[UINavigationController class]], @"Destination not a navigation view");
         UINavigationController *navController = (UINavigationController *)segue.destinationViewController;
-        NSAssert([navController.topViewController conformsToProtocol:@protocol(UTMConfigurationDelegate)], @"Invalid segue destination");
-        id<UTMConfigurationDelegate> controller = (id<UTMConfigurationDelegate>)navController.topViewController;
-        controller.configuration = [[UTMConfiguration alloc] init];
+        NSAssert([navController.topViewController conformsToProtocol:@protocol(UTMQemuConfigurationDelegate)], @"Invalid segue destination");
+        id<UTMQemuConfigurationDelegate> controller = (id<UTMQemuConfigurationDelegate>)navController.topViewController;
+        controller.configuration = [[UTMQemuConfiguration alloc] init];
         controller.configuration.name = [self createNewDefaultName];
     } else if ([segue.identifier isEqualToString:@"startVM"]) {
         NSAssert([segue.destinationViewController isKindOfClass:[VMDisplayMetalViewController class]], @"Destination not a metal view");
         VMDisplayMetalViewController *metalView = (VMDisplayMetalViewController *)segue.destinationViewController;
-        UTMVirtualMachine *vm = (UTMVirtualMachine*) sender;
+        UTMQemuVirtualMachine *vm = (UTMQemuVirtualMachine*) sender;
         metalView.vm = vm;
         vm.delegate = metalView;
         [metalView virtualMachine:vm transitionToState:vm.state];
     } else if ([[segue identifier] isEqualToString:@"startVMConsole"]) {
         NSAssert([segue.destinationViewController isKindOfClass:[VMDisplayTerminalViewController class]], @"Destination not a terminal view");
         VMDisplayTerminalViewController *terminalView = (VMDisplayTerminalViewController *)segue.destinationViewController;
-        UTMVirtualMachine *vm = (UTMVirtualMachine*) sender;
+        UTMQemuVirtualMachine *vm = (UTMQemuVirtualMachine*) sender;
         terminalView.vm = vm;
         vm.delegate = terminalView;
     }
@@ -297,8 +297,8 @@
     VMListViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"vmListCell" forIndexPath:indexPath];
     
     // Configure the cell
-    UTMVirtualMachine *vm = self.vmList[indexPath.row];
-    cell.nameLabel.text = self.vmList[indexPath.row].configuration.name;
+    UTMQemuVirtualMachine *vm = self.vmList[indexPath.row];
+    cell.nameLabel.text = self.vmList[indexPath.row].qemuConfig.name;
     [cell changeState:vm.state image:vm.screenshot.image];
     
     return cell;
@@ -393,7 +393,7 @@
     });
 }
 
-- (void)startVm:(UTMVirtualMachine *)vm {
+- (void)startVm:(UTMQemuVirtualMachine *)vm {
     if (vm.supportedDisplayType == UTMDisplayTypeFullGraphic) {
         [self performSegueWithIdentifier:@"startVM" sender:vm];
     } else if (vm.supportedDisplayType == UTMDisplayTypeConsole) {
@@ -404,16 +404,16 @@
 #pragma mark - Actions
 
 - (IBAction)unwindToMainFromConfiguration:(UIStoryboardSegue*)sender {
-    NSAssert([sender.sourceViewController conformsToProtocol:@protocol(UTMConfigurationDelegate)], @"Invalid source for unwind");
-    id<UTMConfigurationDelegate> source = (id<UTMConfigurationDelegate>)sender.sourceViewController;
+    NSAssert([sender.sourceViewController conformsToProtocol:@protocol(UTMQemuConfigurationDelegate)], @"Invalid source for unwind");
+    id<UTMQemuConfigurationDelegate> source = (id<UTMQemuConfigurationDelegate>)sender.sourceViewController;
     dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
         NSError *err;
         [self workStartedWhenVisible:[NSString stringWithFormat:NSLocalizedString(@"Saving %@...", @"Save VM overlay"), source.configuration.name]];
-        UTMVirtualMachine *vm;
-        if (self.modifyingVM.configuration == source.configuration) {
+        UTMQemuVirtualMachine *vm;
+        if (self.modifyingVM.qemuConfig == source.configuration) {
             vm = self.modifyingVM;
         } else {
-            vm = [[UTMVirtualMachine alloc] initWithConfiguration:source.configuration withDestinationURL:self.documentsPath];
+            vm = (UTMQemuVirtualMachine *)[UTMVirtualMachine virtualMachineWithConfiguration:source.configuration withDestinationURL:self.documentsPath];
         }
         [vm saveUTMWithError:&err];
         [self workCompletedWhenVisible:err.localizedDescription];
@@ -422,13 +422,13 @@
 
 - (IBAction)startVmFromButton:(UIButton *)sender {
     UICollectionViewCell* cell = (UICollectionViewCell*) sender.superview.superview.superview.superview.superview.superview;
-    UTMVirtualMachine* vm = [self vmForCell: cell];
+    UTMQemuVirtualMachine* vm = [self vmForCell: cell];
     [self startVm:vm];
 }
 
 - (IBAction)startVmFromScreen:(UIButton *)sender {
     UICollectionViewCell* cell = (UICollectionViewCell*) sender.superview.superview;
-    UTMVirtualMachine* vm = [self vmForCell: cell];
+    UTMQemuVirtualMachine* vm = [self vmForCell: cell];
     [self startVm:vm];
 }
 
@@ -459,8 +459,8 @@
     if (file.length == 0) {
         [self showAlertSerialized:NSLocalizedString(@"Invalid UTM not imported.", @"VMListViewController") isQuestion:NO completion:nil];
     } else if ([[dest URLByResolvingSymlinksInPath] isEqual:[url URLByResolvingSymlinksInPath]]) {
-        UTMVirtualMachine *found;
-        for (UTMVirtualMachine *vm in self.vmList) {
+        UTMQemuVirtualMachine *found;
+        for (UTMQemuVirtualMachine *vm in self.vmList) {
             if ([vm.path.lastPathComponent isEqualToString:file]) {
                 found = vm;
                 break;

+ 4 - 3
Platform/iOS/UTMDataExtension.swift

@@ -19,17 +19,18 @@ import Foundation
 @available(iOS 14, *)
 extension UTMData {
     private func createDisplay(vm: UTMVirtualMachine) -> VMDisplayViewController {
-        if vm.configuration.displayConsoleOnly {
+        let qvm = vm as! UTMQemuVirtualMachine
+        if qvm.qemuConfig.displayConsoleOnly {
             let vc = VMDisplayTerminalViewController()
             vm.delegate = vc
-            vc.vm = vm
+            vc.vm = qvm
             vc.setupSubviews()
             vc.virtualMachine(vm, transitionTo: vm.state)
             return vc
         } else {
             let vc = VMDisplayMetalViewController()
             vm.delegate = vc
-            vc.vm = vm
+            vc.vm = qvm
             vc.setupSubviews()
             vc.virtualMachine(vm, transitionTo: vm.state)
             return vc

+ 2 - 2
Platform/iOS/VMConfigDrivesView.swift

@@ -20,7 +20,7 @@ import SwiftUI
 
 @available(iOS 14, *)
 struct VMConfigDrivesView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     @State private var createDriveVisible: Bool = false
     @State private var attemptDelete: IndexSet?
     @State private var importDrivePresented: Bool = false
@@ -164,7 +164,7 @@ private struct CreateDrive: View {
 
 @available(iOS 14, *)
 struct VMConfigDrivesView_Previews: PreviewProvider {
-    @ObservedObject static private var config = UTMConfiguration()
+    @ObservedObject static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         Group {

+ 8 - 8
Platform/iOS/VMConfigNetworkPortForwardView.swift

@@ -18,7 +18,7 @@ import SwiftUI
 
 @available(iOS 14, *)
 struct VMConfigNetworkPortForwardView: View {
-    @ObservedObject var config: UTMConfiguration
+    @ObservedObject var config: UTMQemuConfiguration
     
     var body: some View {
         Section(header: HStack { Text("Port Forward") }, footer: EmptyView().padding(.bottom)) {
@@ -52,19 +52,19 @@ struct VMConfigNetworkPortForwardView: View {
 
 @available(iOS 14, *)
 struct PortForwardEdit: View {
-    @StateObject private var configPort: UTMConfigurationPortForward
+    @StateObject private var configPort: UTMQemuConfigurationPortForward
     private let save: () -> Void
     private let delete: (() -> Void)?
     @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
     
-    init(config: UTMConfiguration, index: Int? = nil) {
-        var configPort: UTMConfigurationPortForward
+    init(config: UTMQemuConfiguration, index: Int? = nil) {
+        var configPort: UTMQemuConfigurationPortForward
         if let i = index {
             configPort = config.portForward(for: i)!
         } else {
-            configPort = UTMConfigurationPortForward()
+            configPort = UTMQemuConfigurationPortForward()
         }
-        self._configPort = StateObject<UTMConfigurationPortForward>(wrappedValue: configPort)
+        self._configPort = StateObject<UTMQemuConfigurationPortForward>(wrappedValue: configPort)
         save = {
             config.updatePortForward(at: index ?? config.countPortForwards, withValue: configPort)
         }
@@ -105,14 +105,14 @@ struct PortForwardEdit: View {
 
 @available(iOS 14, *)
 struct VMConfigNetworkPortForwardView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     static var previews: some View {
         Group {
             Form {
                 VMConfigNetworkPortForwardView(config: config)
             }.onAppear {
                 if config.countPortForwards == 0 {
-                    let newConfigPort = UTMConfigurationPortForward()
+                    let newConfigPort = UTMQemuConfigurationPortForward()
                     newConfigPort.protocol = "tcp"
                     newConfigPort.guestAddress = "1.2.3.4"
                     newConfigPort.guestPort = NSNumber(value: 1234)

+ 14 - 10
Platform/iOS/VMSettingsView.swift

@@ -24,60 +24,64 @@ struct VMSettingsView: View {
     @EnvironmentObject private var data: UTMData
     @Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
     
+    var qemuConfig: UTMQemuConfiguration {
+        config as! UTMQemuConfiguration
+    }
+    
     var body: some View {
         NavigationView {
             Form {
                 List {
                     NavigationLink(
-                        destination: VMConfigInfoView(config: config).navigationTitle("Information"),
+                        destination: VMConfigInfoView(config: qemuConfig).navigationTitle("Information"),
                         label: {
                             Label("Information", systemImage: "info.circle")
                                 .labelStyle(RoundRectIconLabelStyle())
                         })
                     NavigationLink(
-                        destination: VMConfigSystemView(config: config).navigationTitle("System"),
+                        destination: VMConfigSystemView(config: qemuConfig).navigationTitle("System"),
                         label: {
                             Label("System", systemImage: "cpu")
                                 .labelStyle(RoundRectIconLabelStyle())
                         })
                     NavigationLink(
-                        destination: VMConfigQEMUView(config: config).navigationTitle("QEMU"),
+                        destination: VMConfigQEMUView(config: qemuConfig).navigationTitle("QEMU"),
                         label: {
                             Label("QEMU", systemImage: "shippingbox")
                                 .labelStyle(RoundRectIconLabelStyle())
                         })
                     NavigationLink(
-                        destination: VMConfigDrivesView(config: config).navigationTitle("Drives"),
+                        destination: VMConfigDrivesView(config: qemuConfig).navigationTitle("Drives"),
                         label: {
                             Label("Drives", systemImage: "internaldrive")
                                 .labelStyle(RoundRectIconLabelStyle())
                         })
                     NavigationLink(
-                        destination: VMConfigDisplayView(config: config).navigationTitle("Display"),
+                        destination: VMConfigDisplayView(config: qemuConfig).navigationTitle("Display"),
                         label: {
                             Label("Display", systemImage: "rectangle.on.rectangle")
                                 .labelStyle(RoundRectIconLabelStyle(color: .green))
                         })
                     NavigationLink(
-                        destination: VMConfigInputView(config: config).navigationTitle("Input"),
+                        destination: VMConfigInputView(config: qemuConfig).navigationTitle("Input"),
                         label: {
                             Label("Input", systemImage: "keyboard")
                                 .labelStyle(RoundRectIconLabelStyle(color: .green))
                         })
                     NavigationLink(
-                        destination: VMConfigNetworkView(config: config).navigationTitle("Network"),
+                        destination: VMConfigNetworkView(config: qemuConfig).navigationTitle("Network"),
                         label: {
                             Label("Network", systemImage: "network")
                                 .labelStyle(RoundRectIconLabelStyle(color: .green))
                         })
                     NavigationLink(
-                        destination: VMConfigSoundView(config: config).navigationTitle("Sound"),
+                        destination: VMConfigSoundView(config: qemuConfig).navigationTitle("Sound"),
                         label: {
                             Label("Sound", systemImage: "speaker.wave.2")
                                 .labelStyle(RoundRectIconLabelStyle(color: .green))
                         })
                     NavigationLink(
-                        destination: VMConfigSharingView(config: config).navigationTitle("Sharing"),
+                        destination: VMConfigSharingView(config: qemuConfig).navigationTitle("Sharing"),
                         label: {
                             Label("Sharing", systemImage: "person.crop.circle.fill")
                                 .labelStyle(RoundRectIconLabelStyle(color: .yellow))
@@ -138,7 +142,7 @@ struct RoundRectIconLabelStyle: LabelStyle {
 
 @available(iOS 14, *)
 struct VMSettingsView_Previews: PreviewProvider {
-    @State static private var config = UTMConfiguration()
+    @State static private var config = UTMQemuConfiguration()
     
     static var previews: some View {
         VMSettingsView(vm: nil, config: config)

Some files were not shown because too many files changed in this diff