Resolve more violations
This commit is contained in:
parent
46b9acf727
commit
e9973129f8
|
@ -2,6 +2,8 @@ disabled_rules:
|
|||
- identifier_name
|
||||
- type_name
|
||||
- file_length
|
||||
- type_body_length
|
||||
- line_length
|
||||
|
||||
excluded:
|
||||
- .build
|
||||
|
|
|
@ -10272,5 +10272,4 @@ extension QEMUArchitecture {
|
|||
case .xtensaeb: return QEMUSerialDevice_xtensaeb.self
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,25 +22,25 @@ import Virtualization
|
|||
final class UTMAppleConfiguration: UTMConfiguration {
|
||||
/// Basic information and icon
|
||||
@Published var _information: UTMConfigurationInfo = .init()
|
||||
|
||||
|
||||
@Published private var _system: UTMAppleConfigurationSystem = .init()
|
||||
|
||||
|
||||
@Published private var _virtualization: UTMAppleConfigurationVirtualization = .init()
|
||||
|
||||
|
||||
@Published private var _sharedDirectories: [UTMAppleConfigurationSharedDirectory] = []
|
||||
|
||||
|
||||
@Published private var _displays: [UTMAppleConfigurationDisplay] = []
|
||||
|
||||
|
||||
@Published private var _drives: [UTMAppleConfigurationDrive] = []
|
||||
|
||||
|
||||
@Published private var _networks: [UTMAppleConfigurationNetwork] = [.init()]
|
||||
|
||||
|
||||
@Published private var _serials: [UTMAppleConfigurationSerial] = []
|
||||
|
||||
|
||||
var backend: UTMBackend {
|
||||
.apple
|
||||
}
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case information = "Information"
|
||||
case system = "System"
|
||||
|
@ -53,10 +53,10 @@ final class UTMAppleConfiguration: UTMConfiguration {
|
|||
case backend = "Backend"
|
||||
case configurationVersion = "ConfigurationVersion"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let backend = try values.decodeIfPresent(UTMBackend.self, forKey: .backend) ?? .unknown
|
||||
|
@ -79,7 +79,7 @@ final class UTMAppleConfiguration: UTMConfiguration {
|
|||
_networks = try values.decode([UTMAppleConfigurationNetwork].self, forKey: .networks)
|
||||
_serials = try values.decode([UTMAppleConfigurationSerial].self, forKey: .serials)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(_information, forKey: .information)
|
||||
|
@ -129,83 +129,81 @@ extension UTMAppleConfigurationError: LocalizedError {
|
|||
get {
|
||||
_information
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_information = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var system: UTMAppleConfigurationSystem {
|
||||
get {
|
||||
_system
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_system = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var virtualization: UTMAppleConfigurationVirtualization {
|
||||
get {
|
||||
_virtualization
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_virtualization = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var sharedDirectories: [UTMAppleConfigurationSharedDirectory] {
|
||||
get {
|
||||
_sharedDirectories
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_sharedDirectories = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var sharedDirectoriesPublisher: Published<[UTMAppleConfigurationSharedDirectory]>.Publisher {
|
||||
get {
|
||||
$_sharedDirectories
|
||||
}
|
||||
$_sharedDirectories
|
||||
}
|
||||
|
||||
|
||||
var displays: [UTMAppleConfigurationDisplay] {
|
||||
get {
|
||||
_displays
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_displays = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var drives: [UTMAppleConfigurationDrive] {
|
||||
get {
|
||||
_drives
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_drives = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var networks: [UTMAppleConfigurationNetwork] {
|
||||
get {
|
||||
_networks
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_networks = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var serials: [UTMAppleConfigurationSerial] {
|
||||
get {
|
||||
_serials
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
_serials = newValue
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
case none = "None"
|
||||
case linux = "Linux"
|
||||
case macOS = "macOS"
|
||||
|
||||
|
||||
var prettyValue: String {
|
||||
switch self {
|
||||
case .none: return NSLocalizedString("None", comment: "UTMAppleConfigurationBoot")
|
||||
|
@ -33,7 +33,7 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var operatingSystem: OperatingSystem
|
||||
var linuxKernelURL: URL?
|
||||
var linuxCommandLine: String?
|
||||
|
@ -41,10 +41,10 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
var efiVariableStorageURL: URL?
|
||||
var vmSavedStateURL: URL?
|
||||
var hasUefiBoot: Bool = false
|
||||
|
||||
|
||||
/// IPSW for installing macOS. Not saved.
|
||||
var macRecoveryIpswURL: URL?
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case operatingSystem = "OperatingSystem"
|
||||
case linuxKernelPath = "LinuxKernelPath"
|
||||
|
@ -53,7 +53,7 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
case efiVariableStoragePath = "EfiVariableStoragePath"
|
||||
case hasUefiBoot = "UEFIBoot"
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
|
||||
throw UTMConfigurationError.invalidDataURL
|
||||
|
@ -81,7 +81,7 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
}
|
||||
vmSavedStateURL = dataURL.appendingPathComponent(QEMUPackageFileName.vmState.rawValue)
|
||||
}
|
||||
|
||||
|
||||
init(for operatingSystem: OperatingSystem, linuxKernelURL: URL? = nil) throws {
|
||||
self.operatingSystem = operatingSystem
|
||||
self.linuxKernelURL = linuxKernelURL
|
||||
|
@ -89,14 +89,14 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
self.hasUefiBoot = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init(from linux: VZLinuxBootLoader) {
|
||||
self.operatingSystem = .linux
|
||||
self.linuxKernelURL = linux.kernelURL
|
||||
self.linuxCommandLine = linux.commandLine
|
||||
self.linuxInitialRamdiskURL = linux.initialRamdiskURL
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(operatingSystem, forKey: .operatingSystem)
|
||||
|
@ -108,7 +108,7 @@ struct UTMAppleConfigurationBoot: Codable {
|
|||
try container.encodeIfPresent(efiVariableStorageURL?.lastPathComponent, forKey: .efiVariableStoragePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func vzBootloader() -> VZBootLoader? {
|
||||
switch operatingSystem {
|
||||
case .none:
|
||||
|
|
|
@ -20,44 +20,44 @@ import Virtualization
|
|||
@available(iOS, unavailable, message: "Apple Virtualization not available on iOS")
|
||||
@available(macOS 11, *)
|
||||
struct UTMAppleConfigurationDisplay: Codable, Identifiable {
|
||||
|
||||
|
||||
var widthInPixels: Int = 1920
|
||||
|
||||
|
||||
var heightInPixels: Int = 1200
|
||||
|
||||
|
||||
var pixelsPerInch: Int = 80
|
||||
|
||||
|
||||
let id = UUID()
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case widthInPixels = "WidthPixels"
|
||||
case heightInPixels = "HeightPixels"
|
||||
case pixelsPerInch = "PixelsPerInch"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
init(width: Int, height: Int, ppi: Int = 80) {
|
||||
widthInPixels = width
|
||||
heightInPixels = height
|
||||
pixelsPerInch = ppi
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
widthInPixels = try values.decode(Int.self, forKey: .widthInPixels)
|
||||
heightInPixels = try values.decode(Int.self, forKey: .heightInPixels)
|
||||
pixelsPerInch = try values.decode(Int.self, forKey: .pixelsPerInch)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(widthInPixels, forKey: .widthInPixels)
|
||||
try container.encode(heightInPixels, forKey: .heightInPixels)
|
||||
try container.encode(pixelsPerInch, forKey: .pixelsPerInch)
|
||||
}
|
||||
|
||||
|
||||
#if arch(arm64)
|
||||
@available(macOS 12, *)
|
||||
init(from config: VZMacGraphicsDisplayConfiguration) {
|
||||
|
@ -65,7 +65,7 @@ struct UTMAppleConfigurationDisplay: Codable, Identifiable {
|
|||
heightInPixels = config.heightInPixels
|
||||
pixelsPerInch = config.pixelsPerInch
|
||||
}
|
||||
|
||||
|
||||
@available(macOS 12, *)
|
||||
func vzMacDisplay() -> VZMacGraphicsDisplayConfiguration {
|
||||
VZMacGraphicsDisplayConfiguration(widthInPixels: widthInPixels,
|
||||
|
@ -73,7 +73,7 @@ struct UTMAppleConfigurationDisplay: Codable, Identifiable {
|
|||
pixelsPerInch: pixelsPerInch)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@available(macOS 13, *)
|
||||
func vzVirtioDisplay() -> VZVirtioGraphicsScanoutConfiguration {
|
||||
VZVirtioGraphicsScanoutConfiguration(widthInPixels: widthInPixels,
|
||||
|
|
|
@ -21,26 +21,26 @@ import Virtualization
|
|||
@available(macOS 11, *)
|
||||
struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
||||
private let bytesInMib = 1048576
|
||||
|
||||
|
||||
var sizeMib: Int = 0
|
||||
var isReadOnly: Bool
|
||||
var isExternal: Bool
|
||||
var imageURL: URL?
|
||||
var imageName: String?
|
||||
|
||||
|
||||
private(set) var id = UUID().uuidString
|
||||
|
||||
|
||||
var isRawImage: Bool {
|
||||
true // always true for Apple VMs
|
||||
}
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case isReadOnly = "ReadOnly"
|
||||
case imageName = "ImageName"
|
||||
case bookmark = "Bookmark" // legacy only
|
||||
case identifier = "Identifier"
|
||||
}
|
||||
|
||||
|
||||
var sizeString: String {
|
||||
let sizeBytes: Int64
|
||||
if let attributes = try? imageURL?.resourceValues(forKeys: [.fileSizeKey]), let fileSize = attributes.fileSize {
|
||||
|
@ -50,19 +50,19 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
|||
}
|
||||
return ByteCountFormatter.string(fromByteCount: sizeBytes, countStyle: .binary)
|
||||
}
|
||||
|
||||
|
||||
init(newSize: Int) {
|
||||
sizeMib = newSize
|
||||
isReadOnly = false
|
||||
isExternal = false
|
||||
}
|
||||
|
||||
|
||||
init(existingURL url: URL?, isExternal: Bool = false) {
|
||||
self.imageURL = url
|
||||
self.isReadOnly = isExternal
|
||||
self.isExternal = isExternal
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
|
||||
throw UTMConfigurationError.invalidDataURL
|
||||
|
@ -85,7 +85,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
|||
isReadOnly = try container.decodeIfPresent(Bool.self, forKey: .isReadOnly) ?? isExternal
|
||||
id = try container.decode(String.self, forKey: .identifier)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
if !isExternal {
|
||||
|
@ -94,7 +94,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
|||
try container.encode(isReadOnly, forKey: .isReadOnly)
|
||||
try container.encode(id, forKey: .identifier)
|
||||
}
|
||||
|
||||
|
||||
func vzDiskImage() throws -> VZDiskImageStorageDeviceAttachment? {
|
||||
if let imageURL = imageURL {
|
||||
return try VZDiskImageStorageDeviceAttachment(url: imageURL, readOnly: isReadOnly)
|
||||
|
@ -102,7 +102,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
imageName?.hash(into: &hasher)
|
||||
sizeMib.hash(into: &hasher)
|
||||
|
@ -110,7 +110,7 @@ struct UTMAppleConfigurationDrive: UTMConfigurationDrive {
|
|||
isExternal.hash(into: &hasher)
|
||||
id.hash(into: &hasher)
|
||||
}
|
||||
|
||||
|
||||
func clone() -> UTMAppleConfigurationDrive {
|
||||
var cloned = self
|
||||
cloned.id = UUID().uuidString
|
||||
|
|
|
@ -21,27 +21,27 @@ import Virtualization
|
|||
@available(macOS 11, *)
|
||||
struct UTMAppleConfigurationGenericPlatform: Codable {
|
||||
var machineIdentifier: Data?
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case machineIdentifier
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
machineIdentifier = try container.decodeIfPresent(Data.self, forKey: .machineIdentifier)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(machineIdentifier, forKey: .machineIdentifier)
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
if #available(macOS 13, *) {
|
||||
machineIdentifier = VZGenericMachineIdentifier().dataRepresentation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@available(macOS 12, *)
|
||||
func vzGenericPlatform() -> VZGenericPlatformConfiguration? {
|
||||
let config = VZGenericPlatformConfiguration()
|
||||
|
|
|
@ -23,13 +23,13 @@ struct UTMAppleConfigurationMacPlatform: Codable {
|
|||
var hardwareModel: Data
|
||||
var machineIdentifier: Data
|
||||
var auxiliaryStorageURL: URL?
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case hardwareModel = "HardwareModel"
|
||||
case machineIdentifier = "MachineIdentifier"
|
||||
case auxiliaryStoragePath = "AuxiliaryStoragePath"
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
guard let dataURL = decoder.userInfo[.dataURL] as? URL else {
|
||||
throw UTMConfigurationError.invalidDataURL
|
||||
|
@ -41,28 +41,28 @@ struct UTMAppleConfigurationMacPlatform: Codable {
|
|||
auxiliaryStorageURL = dataURL.appendingPathComponent(auxiliaryStoragePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(hardwareModel, forKey: .hardwareModel)
|
||||
try container.encode(machineIdentifier, forKey: .machineIdentifier)
|
||||
try container.encodeIfPresent(auxiliaryStorageURL?.lastPathComponent, forKey: .auxiliaryStoragePath)
|
||||
}
|
||||
|
||||
|
||||
#if arch(arm64)
|
||||
@available(macOS 12, *)
|
||||
init(newHardware: VZMacHardwareModel) {
|
||||
hardwareModel = newHardware.dataRepresentation
|
||||
machineIdentifier = VZMacMachineIdentifier().dataRepresentation
|
||||
}
|
||||
|
||||
|
||||
@available(macOS 12, *)
|
||||
init(from config: VZMacPlatformConfiguration) {
|
||||
hardwareModel = config.hardwareModel.dataRepresentation
|
||||
machineIdentifier = config.machineIdentifier.dataRepresentation
|
||||
auxiliaryStorageURL = config.auxiliaryStorage?.url
|
||||
}
|
||||
|
||||
|
||||
@available(macOS 12, *)
|
||||
func vzMacPlatform() -> VZMacPlatformConfiguration? {
|
||||
guard let vzHardwareModel = VZMacHardwareModel(dataRepresentation: hardwareModel) else {
|
||||
|
|
|
@ -23,7 +23,7 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
|
|||
enum NetworkMode: String, CaseIterable, QEMUConstant {
|
||||
case shared = "Shared"
|
||||
case bridged = "Bridged"
|
||||
|
||||
|
||||
var prettyValue: String {
|
||||
switch self {
|
||||
case .shared: return NSLocalizedString("Shared Network", comment: "UTMAppleConfigurationNetwork")
|
||||
|
@ -31,33 +31,33 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var mode: NetworkMode = .shared
|
||||
|
||||
|
||||
/// Unique MAC address.
|
||||
var macAddress: String = VZMACAddress.randomLocallyAdministered().string
|
||||
|
||||
|
||||
/// In bridged mode this is the physical interface to bridge.
|
||||
var bridgeInterface: String?
|
||||
|
||||
|
||||
let id = UUID()
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case mode = "Mode"
|
||||
case macAddress = "MacAddress"
|
||||
case bridgeInterface = "BridgeInterface"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
mode = try values.decode(NetworkMode.self, forKey: .mode)
|
||||
macAddress = try values.decode(String.self, forKey: .macAddress)
|
||||
bridgeInterface = try values.decodeIfPresent(String.self, forKey: .bridgeInterface)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(mode, forKey: .mode)
|
||||
|
@ -66,7 +66,7 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
|
|||
try container.encodeIfPresent(bridgeInterface, forKey: .bridgeInterface)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init?(from config: VZNetworkDeviceConfiguration) {
|
||||
guard let virtioConfig = config as? VZVirtioNetworkDeviceConfiguration else {
|
||||
return nil
|
||||
|
@ -75,13 +75,13 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
|
|||
if let attachment = virtioConfig.attachment as? VZBridgedNetworkDeviceAttachment {
|
||||
mode = .bridged
|
||||
bridgeInterface = attachment.interface.identifier
|
||||
} else if let _ = virtioConfig.attachment as? VZNATNetworkDeviceAttachment {
|
||||
} else if virtioConfig.attachment as? VZNATNetworkDeviceAttachment != nil {
|
||||
mode = .shared
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func vzNetworking() -> VZNetworkDeviceConfiguration? {
|
||||
let config = VZVirtioNetworkDeviceConfiguration()
|
||||
guard let macAddress = VZMACAddress(string: macAddress) else {
|
||||
|
@ -95,11 +95,9 @@ struct UTMAppleConfigurationNetwork: Codable, Identifiable {
|
|||
case .bridged:
|
||||
var found: VZBridgedNetworkInterface?
|
||||
if let bridgeInterface = bridgeInterface {
|
||||
for interface in VZBridgedNetworkInterface.networkInterfaces {
|
||||
if interface.identifier == bridgeInterface {
|
||||
found = interface
|
||||
break
|
||||
}
|
||||
for interface in VZBridgedNetworkInterface.networkInterfaces where interface.identifier == bridgeInterface {
|
||||
found = interface
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// default to first interface if unspecified
|
||||
|
|
|
@ -23,7 +23,7 @@ struct UTMAppleConfigurationSerial: Codable, Identifiable {
|
|||
enum SerialMode: String, CaseIterable, QEMUConstant {
|
||||
case builtin = "Terminal"
|
||||
case ptty = "Ptty"
|
||||
|
||||
|
||||
var prettyValue: String {
|
||||
switch self {
|
||||
case .builtin: return NSLocalizedString("Built-in Terminal", comment: "UTMAppleConfigurationTerminal")
|
||||
|
@ -31,37 +31,37 @@ struct UTMAppleConfigurationSerial: Codable, Identifiable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var mode: SerialMode = .builtin
|
||||
|
||||
|
||||
/// Terminal settings for built-in mode.
|
||||
var terminal: UTMConfigurationTerminal? = .init()
|
||||
|
||||
|
||||
/// Set to read handle before starting VM. Not saved.
|
||||
var fileHandleForReading: FileHandle?
|
||||
|
||||
|
||||
/// Set to write handle before starting VM. Not saved.
|
||||
var fileHandleForWriting: FileHandle?
|
||||
|
||||
|
||||
/// Serial interface used by the VM. Not saved.
|
||||
var interface: UTMSerialPort?
|
||||
|
||||
|
||||
let id = UUID()
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case mode = "Mode"
|
||||
case terminal = "Terminal"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
mode = try values.decode(SerialMode.self, forKey: .mode)
|
||||
terminal = try values.decodeIfPresent(UTMConfigurationTerminal.self, forKey: .terminal)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(mode, forKey: .mode)
|
||||
|
@ -73,7 +73,7 @@ struct UTMAppleConfigurationSerial: Codable, Identifiable {
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func vzSerial() -> VZSerialPortConfiguration? {
|
||||
guard let fileHandleForReading = fileHandleForReading, let fileHandleForWriting = fileHandleForWriting else {
|
||||
return nil
|
||||
|
|
|
@ -22,7 +22,7 @@ import Virtualization
|
|||
@available(macOS 11, *)
|
||||
struct UTMAppleConfigurationSystem: Codable {
|
||||
private let bytesInMib = UInt64(1048576)
|
||||
|
||||
|
||||
static var currentArchitecture: String {
|
||||
#if arch(arm64)
|
||||
"aarch64"
|
||||
|
@ -32,21 +32,21 @@ struct UTMAppleConfigurationSystem: Codable {
|
|||
#error("Unsupported architecture.")
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var architecture: String = Self.currentArchitecture
|
||||
|
||||
|
||||
/// Number of CPU cores to emulate. Set to 0 to match the number of available cores on the host.
|
||||
var cpuCount: Int = 0
|
||||
|
||||
|
||||
/// The RAM of the guest in MiB.
|
||||
var memorySize: Int = 4096
|
||||
|
||||
|
||||
var boot: UTMAppleConfigurationBoot = try! .init(for: .none)
|
||||
|
||||
|
||||
var macPlatform: UTMAppleConfigurationMacPlatform?
|
||||
|
||||
|
||||
var genericPlatform: UTMAppleConfigurationGenericPlatform?
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case architecture = "Architecture"
|
||||
case cpuCount = "CPUCount"
|
||||
|
@ -55,10 +55,10 @@ struct UTMAppleConfigurationSystem: Codable {
|
|||
case macPlatform = "MacPlatform"
|
||||
case genericPlatform = "GenericPlatform"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
architecture = try values.decode(String.self, forKey: .architecture)
|
||||
|
@ -68,7 +68,7 @@ struct UTMAppleConfigurationSystem: Codable {
|
|||
macPlatform = try values.decodeIfPresent(UTMAppleConfigurationMacPlatform.self, forKey: .macPlatform)
|
||||
genericPlatform = try values.decodeIfPresent(UTMAppleConfigurationGenericPlatform.self, forKey: .genericPlatform)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(architecture, forKey: .architecture)
|
||||
|
|
|
@ -76,12 +76,12 @@ extension UTMConfigurationError: LocalizedError {
|
|||
private final class UTMConfigurationStub: Decodable {
|
||||
var backend: UTMBackend
|
||||
var configurationVersion: Int
|
||||
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case backend = "Backend"
|
||||
case configurationVersion = "ConfigurationVersion"
|
||||
}
|
||||
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
backend = try values.decodeIfPresent(UTMBackend.self, forKey: .backend) ?? .unknown
|
||||
|
@ -91,7 +91,7 @@ private final class UTMConfigurationStub: Decodable {
|
|||
|
||||
extension UTMConfiguration {
|
||||
static var dataDirectoryName: String { "Data" }
|
||||
|
||||
|
||||
static func load(from packageURL: URL) throws -> any UTMConfiguration {
|
||||
let scopedAccess = packageURL.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
|
|
|
@ -27,7 +27,7 @@ import Virtualization // for getting network interfaces
|
|||
private func f(_ string: String = "") -> QEMUArgumentFragment {
|
||||
QEMUArgumentFragment(final: string)
|
||||
}
|
||||
|
||||
|
||||
/// Shared between helper and main process to store Unix sockets
|
||||
var socketURL: URL {
|
||||
#if os(iOS) || os(visionOS)
|
||||
|
@ -50,23 +50,23 @@ import Virtualization // for getting network interfaces
|
|||
return parentURL
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/// Return the socket file for communicating with SPICE
|
||||
var spiceSocketURL: URL {
|
||||
socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("spice")
|
||||
}
|
||||
|
||||
|
||||
/// Return the socket file for communicating with SWTPM
|
||||
var swtpmSocketURL: URL {
|
||||
socketURL.appendingPathComponent(information.uuid.uuidString).appendingPathExtension("swtpm")
|
||||
}
|
||||
|
||||
|
||||
/// Combined generated and user specified arguments.
|
||||
@QEMUArgumentBuilder var allArguments: [QEMUArgument] {
|
||||
generatedArguments
|
||||
userArguments
|
||||
}
|
||||
|
||||
|
||||
/// Only UTM generated arguments.
|
||||
@QEMUArgumentBuilder var generatedArguments: [QEMUArgument] {
|
||||
f("-L")
|
||||
|
@ -106,7 +106,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var spiceArguments: [QEMUArgument] {
|
||||
f("-spice")
|
||||
"unix=on"
|
||||
|
@ -129,7 +129,7 @@ import Virtualization // for getting network interfaces
|
|||
f("none")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var displayArguments: [QEMUArgument] {
|
||||
if displays.isEmpty {
|
||||
f("-nographic")
|
||||
|
@ -151,17 +151,17 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var isGLOn: Bool {
|
||||
displays.contains { display in
|
||||
display.hardware.rawValue.contains("-gl-") || display.hardware.rawValue.hasSuffix("-gl")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var isSparc: Bool {
|
||||
system.architecture == .sparc || system.architecture == .sparc64
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var serialArguments: [QEMUArgument] {
|
||||
for i in serials.indices {
|
||||
f("-chardev")
|
||||
|
@ -204,7 +204,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var cpuArguments: [QEMUArgument] {
|
||||
if system.cpu.rawValue == system.architecture.cpuType.default.rawValue {
|
||||
// if default and not hypervisor, we don't pass any -cpu argument for x86 and use host for ARM
|
||||
|
@ -241,14 +241,14 @@ import Virtualization // for getting network interfaces
|
|||
"threads=\(emulatedCpuCount.1/emulatedCpuCount.0)"
|
||||
f()
|
||||
}
|
||||
|
||||
|
||||
private static func sysctlIntRead(_ name: String) -> UInt64 {
|
||||
var value: UInt64 = 0
|
||||
var size = MemoryLayout<UInt64>.size
|
||||
sysctlbyname(name, &value, &size, nil, 0)
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
private var emulatedCpuCount: (Int, Int) {
|
||||
let singleCpu = (1, 1)
|
||||
let hostPhysicalCpu = Int(Self.sysctlIntRead("hw.physicalcpu"))
|
||||
|
@ -282,23 +282,23 @@ import Virtualization // for getting network interfaces
|
|||
return singleCpu
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private var isHypervisorUsed: Bool {
|
||||
system.architecture.hasHypervisorSupport && qemu.hasHypervisor
|
||||
}
|
||||
|
||||
|
||||
private var isTSOUsed: Bool {
|
||||
system.architecture.hasTSOSupport && qemu.hasTSO
|
||||
}
|
||||
|
||||
|
||||
private var isUsbUsed: Bool {
|
||||
system.architecture.hasUsbSupport && system.target.hasUsbSupport && input.usbBusSupport != .disabled
|
||||
}
|
||||
|
||||
|
||||
private var isSecureBootUsed: Bool {
|
||||
system.architecture.hasSecureBootSupport && system.target.hasSecureBootSupport && qemu.hasTPMDevice
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var machineArguments: [QEMUArgument] {
|
||||
f("-machine")
|
||||
system.target
|
||||
|
@ -327,7 +327,7 @@ import Virtualization // for getting network interfaces
|
|||
f()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var machineProperties: String {
|
||||
let target = system.target.rawValue
|
||||
let architecture = system.architecture.rawValue
|
||||
|
@ -367,7 +367,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
return properties
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var architectureArguments: [QEMUArgument] {
|
||||
if system.architecture == .x86_64 || system.architecture == .i386 {
|
||||
f("-global")
|
||||
|
@ -402,7 +402,7 @@ import Virtualization // for getting network interfaces
|
|||
system.memorySize
|
||||
f()
|
||||
}
|
||||
|
||||
|
||||
private var hasCustomBios: Bool {
|
||||
for drive in drives {
|
||||
if drive.imageType == .disk || drive.imageType == .cd {
|
||||
|
@ -415,11 +415,11 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
private var resourceURL: URL {
|
||||
Bundle.main.url(forResource: "qemu", withExtension: nil)!
|
||||
}
|
||||
|
||||
|
||||
private var soundBackend: UTMQEMUSoundBackend {
|
||||
let value = UserDefaults.standard.integer(forKey: "QEMUSoundBackend")
|
||||
if let backend = UTMQEMUSoundBackend(rawValue: value), backend != .qemuSoundBackendMax {
|
||||
|
@ -428,7 +428,7 @@ import Virtualization // for getting network interfaces
|
|||
return .qemuSoundBackendDefault
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var useCoreAudioBackend: Bool {
|
||||
#if os(iOS) || os(visionOS)
|
||||
return false
|
||||
|
@ -444,7 +444,7 @@ import Virtualization // for getting network interfaces
|
|||
return false
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var soundArguments: [QEMUArgument] {
|
||||
if useCoreAudioBackend {
|
||||
f("-audiodev")
|
||||
|
@ -478,7 +478,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var drivesArguments: [QEMUArgument] {
|
||||
var busInterfaceMap: [String: Int] = [:]
|
||||
for drive in drives {
|
||||
|
@ -506,7 +506,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// These machines are hard coded to have one IDE unit per bus in QEMU
|
||||
private var isIdeInterfaceSingleUnit: Bool {
|
||||
system.target.rawValue.contains("q35") ||
|
||||
|
@ -516,7 +516,7 @@ import Virtualization // for getting network interfaces
|
|||
system.target.rawValue == "midway" ||
|
||||
system.target.rawValue == "xlnx_zcu102"
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private func driveArgument(for drive: UTMQemuConfigurationDrive, busInterfaceMap: inout [String: Int]) -> [QEMUArgument] {
|
||||
let isRemovable = drive.imageType == .cd || drive.isExternal
|
||||
let isCd = drive.imageType == .cd && drive.interface != .floppy
|
||||
|
@ -657,7 +657,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
f()
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var usbArguments: [QEMUArgument] {
|
||||
if system.target.rawValue.hasPrefix("virt") {
|
||||
f("-device")
|
||||
|
@ -704,7 +704,7 @@ import Virtualization // for getting network interfaces
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private func parseNetworkSubnet(from network: UTMQemuConfigurationNetwork) -> (start: String, end: String, mask: String)? {
|
||||
guard let net = network.vlanGuestAddress else {
|
||||
return nil
|
||||
|
@ -742,13 +742,13 @@ import Virtualization // for getting network interfaces
|
|||
let netmaskStr = String(cString: inet_ntoa(netmask))
|
||||
return (network.vlanDhcpStartAddress ?? firstAddrStr, network.vlanDhcpEndAddress ?? lastAddrStr, netmaskStr)
|
||||
}
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
private var defaultBridgedInterface: String {
|
||||
VZBridgedNetworkInterface.networkInterfaces.first?.identifier ?? "en0"
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var networkArguments: [QEMUArgument] {
|
||||
for i in networks.indices {
|
||||
if isSparc {
|
||||
|
@ -844,14 +844,14 @@ import Virtualization // for getting network interfaces
|
|||
f("none")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var isSpiceAgentUsed: Bool {
|
||||
guard system.architecture.hasAgentSupport && system.target.hasAgentSupport else {
|
||||
return false
|
||||
}
|
||||
return sharing.hasClipboardSharing || sharing.directoryShareMode == .webdav || displays.contains(where: { $0.isDynamicResolution })
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var sharingArguments: [QEMUArgument] {
|
||||
if system.architecture.hasAgentSupport && system.target.hasAgentSupport {
|
||||
f("-device")
|
||||
|
@ -894,14 +894,14 @@ import Virtualization // for getting network interfaces
|
|||
"mount_tag=share"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func cleanupName(_ name: String) -> String {
|
||||
let allowedCharacterSet = CharacterSet.alphanumerics.union(.whitespaces)
|
||||
let filteredString = name.components(separatedBy: allowedCharacterSet.inverted)
|
||||
.joined(separator: "")
|
||||
return filteredString
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var miscArguments: [QEMUArgument] {
|
||||
f("-name")
|
||||
f(cleanupName(information.name))
|
||||
|
@ -926,7 +926,7 @@ import Virtualization // for getting network interfaces
|
|||
tpmArguments
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@QEMUArgumentBuilder private var tpmArguments: [QEMUArgument] {
|
||||
f("-chardev")
|
||||
"socket"
|
||||
|
|
|
@ -19,15 +19,15 @@ import SwiftUI
|
|||
struct SizeTextField: View {
|
||||
@Binding var sizeMib: Int
|
||||
@State private var isGiB: Bool = true
|
||||
|
||||
|
||||
private let mibToGib = 1024
|
||||
let minSizeMib: Int
|
||||
|
||||
|
||||
init(_ sizeMib: Binding<Int>, minSizeMib: Int = 1) {
|
||||
_sizeMib = sizeMib
|
||||
self.minSizeMib = minSizeMib
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
NumberTextField("Size", number: Binding<Int>(get: {
|
||||
|
@ -48,7 +48,7 @@ struct SizeTextField: View {
|
|||
}).buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func validateSize(editing: Bool) {
|
||||
guard !editing else {
|
||||
return
|
||||
|
@ -57,7 +57,7 @@ struct SizeTextField: View {
|
|||
sizeMib = minSizeMib
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func convertToMib(fromSize size: Int) -> Int {
|
||||
if isGiB {
|
||||
return size * mibToGib
|
||||
|
@ -65,7 +65,7 @@ struct SizeTextField: View {
|
|||
return size
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func convertToDisplay(fromSizeMib sizeMib: Int) -> Int {
|
||||
if isGiB {
|
||||
return sizeMib / mibToGib
|
||||
|
|
|
@ -22,14 +22,14 @@ struct Spinner: NSViewRepresentable {
|
|||
enum Size: RawRepresentable {
|
||||
case regular
|
||||
case large
|
||||
|
||||
|
||||
var rawValue: NSControl.ControlSize {
|
||||
switch self {
|
||||
case .regular: return .regular
|
||||
case .large: return .large
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init?(rawValue: NSControl.ControlSize) {
|
||||
switch rawValue {
|
||||
case .regular:
|
||||
|
@ -41,9 +41,9 @@ struct Spinner: NSViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let size: Size
|
||||
|
||||
|
||||
func makeNSView(context: Context) -> NSProgressIndicator {
|
||||
let view = NSProgressIndicator()
|
||||
view.controlSize = size.rawValue
|
||||
|
@ -51,7 +51,7 @@ struct Spinner: NSViewRepresentable {
|
|||
view.startAnimation(self)
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
func updateNSView(_ nsView: NSProgressIndicator, context: Context) {
|
||||
}
|
||||
}
|
||||
|
@ -60,14 +60,14 @@ struct Spinner: UIViewRepresentable {
|
|||
enum Size: RawRepresentable {
|
||||
case regular
|
||||
case large
|
||||
|
||||
|
||||
var rawValue: UIActivityIndicatorView.Style {
|
||||
switch self {
|
||||
case .regular: return .medium
|
||||
case .large: return .large
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init?(rawValue: UIActivityIndicatorView.Style) {
|
||||
switch rawValue {
|
||||
case .medium:
|
||||
|
@ -79,16 +79,16 @@ struct Spinner: UIViewRepresentable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let size: Size
|
||||
|
||||
|
||||
func makeUIView(context: Context) -> UIActivityIndicatorView {
|
||||
let view = UIActivityIndicatorView(style: size.rawValue)
|
||||
view.color = .white
|
||||
view.startAnimating()
|
||||
return view
|
||||
}
|
||||
|
||||
|
||||
func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@ import SwiftUI
|
|||
struct VMWizardContent<Content>: View where Content: View {
|
||||
let titleKey: LocalizedStringKey
|
||||
let content: Content
|
||||
|
||||
|
||||
init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) {
|
||||
self.titleKey = titleKey
|
||||
self.content = content()
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
Text(titleKey)
|
||||
|
|
|
@ -18,7 +18,7 @@ import SwiftUI
|
|||
|
||||
struct VMWizardDrivesView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Storage") {
|
||||
Section {
|
||||
|
@ -37,14 +37,13 @@ struct VMWizardDrivesView: View {
|
|||
} header: {
|
||||
Text("Size")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VMWizardDrivesView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardDrivesView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import Virtualization
|
|||
|
||||
struct VMWizardHardwareView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
|
||||
|
||||
var minCores: Int {
|
||||
#if canImport(Virtualization)
|
||||
VZVirtualMachineConfiguration.minimumAllowedCPUCount
|
||||
|
@ -29,7 +29,7 @@ struct VMWizardHardwareView: View {
|
|||
1
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var maxCores: Int {
|
||||
#if canImport(Virtualization)
|
||||
VZVirtualMachineConfiguration.maximumAllowedCPUCount
|
||||
|
@ -37,7 +37,7 @@ struct VMWizardHardwareView: View {
|
|||
Int(sysctlIntRead("hw.ncpu"))
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var minMemoryMib: Int {
|
||||
#if canImport(Virtualization)
|
||||
Int(VZVirtualMachineConfiguration.minimumAllowedMemorySize / UInt64(wizardState.bytesInMib))
|
||||
|
@ -45,7 +45,7 @@ struct VMWizardHardwareView: View {
|
|||
8
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var maxMemoryMib: Int {
|
||||
#if canImport(Virtualization)
|
||||
Int(VZVirtualMachineConfiguration.maximumAllowedMemorySize / UInt64(wizardState.bytesInMib))
|
||||
|
@ -53,7 +53,7 @@ struct VMWizardHardwareView: View {
|
|||
sysctlIntRead("hw.memsize")
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Hardware") {
|
||||
if !wizardState.useVirtualization {
|
||||
|
@ -65,7 +65,7 @@ struct VMWizardHardwareView: View {
|
|||
} header: {
|
||||
Text("Architecture")
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
VMConfigConstantPicker(selection: $wizardState.systemTarget, type: wizardState.systemArchitecture.targetType)
|
||||
} header: {
|
||||
|
@ -84,7 +84,7 @@ struct VMWizardHardwareView: View {
|
|||
} header: {
|
||||
Text("Memory")
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
HStack {
|
||||
Stepper(value: $wizardState.systemCpuCount, in: minCores...maxCores) {
|
||||
|
@ -106,14 +106,11 @@ struct VMWizardHardwareView: View {
|
|||
} header: {
|
||||
Text("CPU")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if !wizardState.useAppleVirtualization && wizardState.operatingSystem == .Linux {
|
||||
DetailedSection("Hardware OpenGL Acceleration", description: "There are known issues in some newer Linux drivers including black screen, broken compositing, and apps failing to render.") {
|
||||
Toggle("Enable hardware OpenGL acceleration", isOn: $wizardState.isGLEnabled)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.textFieldStyle(.roundedBorder)
|
||||
|
@ -130,7 +127,7 @@ struct VMWizardHardwareView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func sysctlIntRead(_ name: String) -> Int {
|
||||
var value: Int = 0
|
||||
var size = MemoryLayout<UInt64>.size
|
||||
|
@ -141,7 +138,7 @@ struct VMWizardHardwareView: View {
|
|||
|
||||
struct VMWizardHardwareView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardHardwareView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ struct VMWizardOSLinuxView: View {
|
|||
case rootImage
|
||||
case bootImage
|
||||
}
|
||||
|
||||
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
@State private var selectImage: SelectImage = .kernel
|
||||
|
@ -35,7 +35,7 @@ struct VMWizardOSLinuxView: View {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Linux") {
|
||||
#if os(macOS)
|
||||
|
@ -45,7 +45,7 @@ struct VMWizardOSLinuxView: View {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
Section {
|
||||
Toggle("Boot from kernel image", isOn: $wizardState.useLinuxKernel)
|
||||
.help("If set, boot directly from a raw kernel image and initrd. Otherwise, boot from a supported ISO.")
|
||||
|
@ -64,7 +64,7 @@ struct VMWizardOSLinuxView: View {
|
|||
} header: {
|
||||
Text("Boot Image Type")
|
||||
}
|
||||
|
||||
|
||||
#if arch(arm64)
|
||||
if #available(macOS 13, *), wizardState.useAppleVirtualization {
|
||||
Section {
|
||||
|
@ -77,9 +77,9 @@ struct VMWizardOSLinuxView: View {
|
|||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if wizardState.useLinuxKernel {
|
||||
|
||||
|
||||
Section {
|
||||
FileBrowseField(url: $wizardState.linuxKernelURL, isFileImporterPresented: $isFileImporterPresented, hasClearButton: false) {
|
||||
selectImage = .kernel
|
||||
|
@ -91,7 +91,7 @@ struct VMWizardOSLinuxView: View {
|
|||
Text("Linux kernel (required)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
FileBrowseField(url: $wizardState.linuxInitialRamdiskURL, isFileImporterPresented: $isFileImporterPresented) {
|
||||
selectImage = .initialRamdisk
|
||||
|
@ -103,7 +103,7 @@ struct VMWizardOSLinuxView: View {
|
|||
Text("Linux initial ramdisk (optional)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
FileBrowseField(url: $wizardState.linuxRootImageURL, isFileImporterPresented: $isFileImporterPresented) {
|
||||
selectImage = .rootImage
|
||||
|
@ -119,7 +119,7 @@ struct VMWizardOSLinuxView: View {
|
|||
} header: {
|
||||
Text("Boot ISO Image (optional)")
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
TextField("Boot Arguments", text: $wizardState.linuxBootArguments)
|
||||
} header: {
|
||||
|
@ -137,12 +137,10 @@ struct VMWizardOSLinuxView: View {
|
|||
if wizardState.isBusy {
|
||||
Spinner(size: .large)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.data], onCompletion: processImage)
|
||||
}
|
||||
|
||||
|
||||
private func processImage(_ result: Result<URL, Error>) {
|
||||
wizardState.busyWorkAsync {
|
||||
let url = try result.get()
|
||||
|
@ -165,7 +163,7 @@ struct VMWizardOSLinuxView: View {
|
|||
|
||||
struct VMWizardOSLinuxView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardOSLinuxView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ struct VMWizardOSMacView: View {
|
|||
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.ipsw], onCompletion: processIpsw)
|
||||
.onDrop(of: [.fileURL], delegate: self)
|
||||
}
|
||||
|
||||
|
||||
private func processIpsw(_ result: Result<URL, Error>) {
|
||||
wizardState.busyWorkAsync {
|
||||
#if arch(arm64)
|
||||
|
@ -120,7 +120,7 @@ extension VMWizardOSMacView: DropDelegate {
|
|||
@available(macOS 12, *)
|
||||
struct VMWizardOSMacView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardOSMacView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import SwiftUI
|
|||
struct VMWizardOSOtherView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Other") {
|
||||
if !wizardState.isSkipBootImage {
|
||||
|
@ -41,7 +41,7 @@ struct VMWizardOSOtherView: View {
|
|||
}
|
||||
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.data], onCompletion: processImage)
|
||||
}
|
||||
|
||||
|
||||
private func processImage(_ result: Result<URL, Error>) {
|
||||
wizardState.busyWorkAsync {
|
||||
let url = try result.get()
|
||||
|
@ -54,7 +54,7 @@ struct VMWizardOSOtherView: View {
|
|||
|
||||
struct VMWizardOSOtherView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardOSOtherView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -80,12 +80,12 @@ struct VMWizardOSView: View {
|
|||
struct OperatingSystem: View {
|
||||
let imageName: String
|
||||
let name: LocalizedStringKey
|
||||
|
||||
|
||||
private var imageURL: URL {
|
||||
let path = Bundle.main.path(forResource: imageName, ofType: "png", inDirectory: "Icons")!
|
||||
return URL(fileURLWithPath: path)
|
||||
}
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
private var icon: Image {
|
||||
Image(nsImage: NSImage(byReferencing: imageURL))
|
||||
|
@ -95,7 +95,7 @@ struct OperatingSystem: View {
|
|||
Image(uiImage: UIImage(contentsOfURL: imageURL)!)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
icon
|
||||
|
@ -111,7 +111,7 @@ struct OperatingSystem: View {
|
|||
|
||||
struct VMWizardOSView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardOSView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ struct VMWizardOSWindowsView: View {
|
|||
@ObservedObject var wizardState: VMWizardState
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
@State private var useVhdx: Bool = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Windows") {
|
||||
Section {
|
||||
|
@ -35,7 +35,7 @@ struct VMWizardOSWindowsView: View {
|
|||
wizardState.isGuestToolsInstallRequested = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if wizardState.isWindows10OrHigher {
|
||||
Toggle("Import VHDX Image", isOn: $useVhdx)
|
||||
#if os(macOS)
|
||||
|
@ -77,14 +77,14 @@ struct VMWizardOSWindowsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Section {
|
||||
if useVhdx {
|
||||
FileBrowseField(url: $wizardState.windowsBootVhdx, isFileImporterPresented: $isFileImporterPresented, hasClearButton: false)
|
||||
} else {
|
||||
FileBrowseField(url: $wizardState.bootImageURL, isFileImporterPresented: $isFileImporterPresented, hasClearButton: false)
|
||||
}
|
||||
|
||||
|
||||
if wizardState.isBusy {
|
||||
Spinner(size: .large)
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ struct VMWizardOSWindowsView: View {
|
|||
.disabled(!wizardState.systemBootUefi)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Disabled on iOS 14 due to a SwiftUI layout bug
|
||||
// Disabled for non-Windows 10 installs due to autounattend version
|
||||
if #available(iOS 15, *), wizardState.isWindows10OrHigher {
|
||||
|
@ -119,7 +119,7 @@ struct VMWizardOSWindowsView: View {
|
|||
}
|
||||
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.data], onCompletion: processImage)
|
||||
}
|
||||
|
||||
|
||||
private func processImage(_ result: Result<URL, Error>) {
|
||||
wizardState.busyWorkAsync {
|
||||
let url = try result.get()
|
||||
|
@ -140,7 +140,7 @@ struct VMWizardOSWindowsView: View {
|
|||
|
||||
struct VMWizardOSWindowsView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardOSWindowsView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -19,16 +19,16 @@ import SwiftUI
|
|||
struct VMWizardSharingView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
@State private var isFileImporterPresented: Bool = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Shared Directory") {
|
||||
DetailedSection("Shared Directory Path", description: "Optionally select a directory to make accessible inside the VM. Note that support for shared directories varies by the guest operating system and may require additional guest drivers to be installed. See UTM support pages for more details.") {
|
||||
FileBrowseField(url: $wizardState.sharingDirectoryURL, isFileImporterPresented: $isFileImporterPresented)
|
||||
|
||||
|
||||
if !wizardState.useAppleVirtualization {
|
||||
Toggle("Share is read only", isOn: $wizardState.sharingReadOnly)
|
||||
}
|
||||
|
||||
|
||||
if wizardState.isBusy {
|
||||
Spinner(size: .large)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ struct VMWizardSharingView: View {
|
|||
}
|
||||
.fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.folder], onCompletion: processDirectory)
|
||||
}
|
||||
|
||||
|
||||
private func processDirectory(_ result: Result<URL, Error>) {
|
||||
wizardState.busyWorkAsync {
|
||||
let url = try result.get()
|
||||
|
@ -49,7 +49,7 @@ struct VMWizardSharingView: View {
|
|||
|
||||
struct VMWizardSharingView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardSharingView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import Virtualization
|
|||
|
||||
struct VMWizardStartView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
|
||||
|
||||
var isVirtualizationSupported: Bool {
|
||||
#if os(macOS)
|
||||
VZVirtualMachine.isSupported && !processIsTranslated()
|
||||
|
@ -29,7 +29,7 @@ struct VMWizardStartView: View {
|
|||
jb_has_hypervisor()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var isEmulationSupported: Bool {
|
||||
#if WITH_QEMU_TCI
|
||||
true
|
||||
|
@ -37,7 +37,7 @@ struct VMWizardStartView: View {
|
|||
Main.jitAvailable
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VMWizardContent("Start") {
|
||||
Section {
|
||||
|
@ -127,7 +127,7 @@ struct VMWizardStartView: View {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func processIsTranslated() -> Bool {
|
||||
let key = "sysctl.proc_translated"
|
||||
var ret = Int32(0)
|
||||
|
@ -143,7 +143,7 @@ struct VMWizardStartView: View {
|
|||
|
||||
struct VMWizardStartView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardStartView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import SwiftUI
|
|||
struct VMWizardSummaryView: View {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
@EnvironmentObject private var data: UTMData
|
||||
|
||||
|
||||
var storageDescription: String {
|
||||
var size = Int64(wizardState.storageSizeGib * wizardState.bytesInGib)
|
||||
#if arch(arm64)
|
||||
|
@ -31,7 +31,7 @@ struct VMWizardSummaryView: View {
|
|||
#endif
|
||||
return ByteCountFormatter.string(fromByteCount: size, countStyle: .binary)
|
||||
}
|
||||
|
||||
|
||||
var coreDescription: String {
|
||||
let cores = wizardState.systemCpuCount
|
||||
if cores == 0 {
|
||||
|
@ -40,7 +40,7 @@ struct VMWizardSummaryView: View {
|
|||
return String.localizedStringWithFormat(NSLocalizedString("%lld Cores", comment: "VMWizardSummaryView"), cores)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
#if os(macOS)
|
||||
|
@ -104,7 +104,7 @@ struct VMWizardSummaryView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var info: some View {
|
||||
Group {
|
||||
TextField("Name", text: $wizardState.name.bound)
|
||||
|
@ -115,7 +115,7 @@ struct VMWizardSummaryView: View {
|
|||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var system: some View {
|
||||
Group {
|
||||
TextField("Engine", text: .constant(NSLocalizedString(wizardState.useAppleVirtualization ? "Apple Virtualization" : "QEMU", comment: "VMWizardSummaryView")))
|
||||
|
@ -132,7 +132,7 @@ struct VMWizardSummaryView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var boot: some View {
|
||||
Group {
|
||||
TextField("Operating System", text: .constant(NSLocalizedString(wizardState.operatingSystem.rawValue, comment: "VMWizardSummaryView")))
|
||||
|
@ -164,7 +164,7 @@ struct VMWizardSummaryView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var sharing: some View {
|
||||
Group {
|
||||
Toggle("Share Directory", isOn: .constant(wizardState.sharingDirectoryURL != nil))
|
||||
|
@ -178,7 +178,7 @@ struct VMWizardSummaryView: View {
|
|||
|
||||
struct VMWizardSummaryView_Previews: PreviewProvider {
|
||||
@StateObject static var wizardState = VMWizardState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMWizardSummaryView(wizardState: wizardState)
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ extension UTMData {
|
|||
let session = VMSessionState(for: wrapped as! UTMQemuVirtualMachine)
|
||||
session.start()
|
||||
}
|
||||
|
||||
|
||||
func stop(vm: VMData) {
|
||||
guard let wrapped = vm.wrapped else {
|
||||
return
|
||||
|
@ -38,11 +38,11 @@ extension UTMData {
|
|||
wrapped.requestVmDeleteState()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func close(vm: VMData) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
func tryClickAtPoint(point: CGPoint, button: CSInputButton) {
|
||||
if let vc = vmVC as? VMDisplayMetalViewController, let input = vc.vmInput {
|
||||
input.sendMouseButton(button, pressed: true)
|
||||
|
@ -51,7 +51,7 @@ extension UTMData {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func trySendTextSpice(_ text: String) {
|
||||
if let vc = vmVC as? VMDisplayMetalViewController {
|
||||
#if !os(visionOS) // FIXME: broken in visionOS
|
||||
|
|
|
@ -24,14 +24,14 @@ struct VMToolbarView: View {
|
|||
@State private var isIdle: Bool = false
|
||||
@State private var dragPosition: CGPoint = .zero
|
||||
@State private var shortIdleTask: DispatchWorkItem?
|
||||
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
@EnvironmentObject private var session: VMSessionState
|
||||
@StateObject private var longIdleTimeout = LongIdleTimeout()
|
||||
|
||||
|
||||
@Binding var state: VMWindowState
|
||||
|
||||
|
||||
private var spacing: CGFloat {
|
||||
let direction: CGFloat
|
||||
let distance: CGFloat
|
||||
|
@ -47,7 +47,7 @@ struct VMToolbarView: View {
|
|||
}
|
||||
return direction * distance
|
||||
}
|
||||
|
||||
|
||||
private var nameOfHideIcon: String {
|
||||
if location == .topLeft || location == .bottomLeft {
|
||||
return "chevron.right"
|
||||
|
@ -55,7 +55,7 @@ struct VMToolbarView: View {
|
|||
return "chevron.left"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var nameOfShowIcon: String {
|
||||
if location == .topLeft || location == .bottomLeft {
|
||||
return "chevron.left"
|
||||
|
@ -63,7 +63,7 @@ struct VMToolbarView: View {
|
|||
return "chevron.right"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var toolbarToggleOpacity: Double {
|
||||
if state.device != nil && !state.isBusy && state.isRunning && isCollapsed && !isMoving {
|
||||
if !longIdleTimeout.isUserInteracting {
|
||||
|
@ -77,7 +77,7 @@ struct VMToolbarView: View {
|
|||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Group {
|
||||
|
@ -101,7 +101,7 @@ struct VMToolbarView: View {
|
|||
Label("Restart", systemImage: "restart")
|
||||
}.offset(offset(for: 6))
|
||||
Button {
|
||||
if case .serial(_, _) = state.device {
|
||||
if case .serial = state.device {
|
||||
let template = session.qemuConfig.serials[state.device!.configIndex].terminal?.resizeCommand
|
||||
state.toggleDisplayResize(command: template)
|
||||
} else {
|
||||
|
@ -178,7 +178,7 @@ struct VMToolbarView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func withOptionalAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result {
|
||||
if UIAccessibility.isReduceMotionEnabled {
|
||||
return try body()
|
||||
|
@ -186,7 +186,7 @@ struct VMToolbarView: View {
|
|||
return try withAnimation(animation, body)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func position(for geometry: GeometryProxy) -> CGPoint {
|
||||
let yoffset: CGFloat = 48
|
||||
var xoffset: CGFloat = 48
|
||||
|
@ -207,7 +207,7 @@ struct VMToolbarView: View {
|
|||
return CGPoint(x: xoffset, y: geometry.size.height - yoffset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func closestLocation(to point: CGPoint, for geometry: GeometryProxy) -> ToolbarLocation {
|
||||
if point.x < geometry.size.width/2 && point.y < geometry.size.height/2 {
|
||||
return .topLeft
|
||||
|
@ -219,7 +219,7 @@ struct VMToolbarView: View {
|
|||
return .topRight
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func offset(for index: Int) -> CGSize {
|
||||
var sub = 0
|
||||
if !session.vm.hasUsbRedirection && index >= 4 {
|
||||
|
@ -228,7 +228,7 @@ struct VMToolbarView: View {
|
|||
let x = isCollapsed ? 0 : -CGFloat(index-sub)*spacing
|
||||
return CGSize(width: x, height: 0)
|
||||
}
|
||||
|
||||
|
||||
private func resetIdle() {
|
||||
if let task = shortIdleTask {
|
||||
task.cancel()
|
||||
|
@ -254,10 +254,10 @@ enum ToolbarLocation: Int {
|
|||
protocol ToolbarButtonBaseStyle<Label, Content> {
|
||||
associatedtype Label: View
|
||||
associatedtype Content: View
|
||||
|
||||
|
||||
var horizontalSizeClass: UserInterfaceSizeClass? { get }
|
||||
var verticalSizeClass: UserInterfaceSizeClass? { get }
|
||||
|
||||
|
||||
func makeBodyBase(label: Label, isPressed: Bool) -> Content
|
||||
}
|
||||
|
||||
|
@ -265,7 +265,7 @@ extension ToolbarButtonBaseStyle {
|
|||
private var size: CGFloat {
|
||||
(horizontalSizeClass == .compact || verticalSizeClass == .compact) ? 32 : 48
|
||||
}
|
||||
|
||||
|
||||
func makeBodyBase(label: Label, isPressed: Bool) -> some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
|
@ -283,16 +283,15 @@ extension ToolbarButtonBaseStyle {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
struct ToolbarButtonStyle: ButtonStyle, ToolbarButtonBaseStyle {
|
||||
typealias Label = Configuration.Label
|
||||
|
||||
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClassEnvironment
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClassEnvironment
|
||||
|
||||
|
||||
var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
var verticalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
|
||||
init(horizontalSizeClass: UserInterfaceSizeClass? = nil, verticalSizeClass: UserInterfaceSizeClass? = nil) {
|
||||
if horizontalSizeClass != nil {
|
||||
self.horizontalSizeClass = horizontalSizeClass
|
||||
|
@ -313,10 +312,10 @@ struct ToolbarButtonStyle: ButtonStyle, ToolbarButtonBaseStyle {
|
|||
|
||||
struct ToolbarMenuStyle: MenuStyle, ToolbarButtonBaseStyle {
|
||||
typealias Label = Menu<Configuration.Label, Configuration.Content>
|
||||
|
||||
|
||||
@Environment(\.horizontalSizeClass) internal var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) internal var verticalSizeClass
|
||||
|
||||
|
||||
func makeBody(configuration: Configuration) -> some View {
|
||||
return makeBodyBase(label: Menu(configuration), isPressed: false)
|
||||
}
|
||||
|
@ -327,7 +326,7 @@ struct Shake: GeometryEffect {
|
|||
var amount: CGFloat = 8
|
||||
var shakesPerUnit = 3
|
||||
var animatableData: CGFloat
|
||||
|
||||
|
||||
init(shake: Bool) {
|
||||
animatableData = shake ? 1.0 : 0.0
|
||||
}
|
||||
|
@ -343,8 +342,8 @@ extension ButtonStyle where Self == ToolbarButtonStyle {
|
|||
static var toolbar: ToolbarButtonStyle {
|
||||
ToolbarButtonStyle()
|
||||
}
|
||||
|
||||
// this is needed to workaround a SwiftUI bug on < iOS 15
|
||||
|
||||
// This is needed to workaround a SwiftUI bug on < iOS 15
|
||||
static func toolbar(horizontalSizeClass: UserInterfaceSizeClass?, verticalSizeClass: UserInterfaceSizeClass?) -> ToolbarButtonStyle {
|
||||
ToolbarButtonStyle(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass)
|
||||
}
|
||||
|
@ -358,9 +357,9 @@ extension MenuStyle where Self == ToolbarMenuStyle {
|
|||
|
||||
@MainActor private class LongIdleTimeout: ObservableObject {
|
||||
private var longIdleTask: DispatchWorkItem?
|
||||
|
||||
|
||||
@Published var isUserInteracting: Bool = true
|
||||
|
||||
|
||||
private func setIsUserInteracting(_ value: Bool) {
|
||||
if !UIAccessibility.isReduceMotionEnabled {
|
||||
withAnimation {
|
||||
|
@ -370,7 +369,7 @@ extension MenuStyle where Self == ToolbarMenuStyle {
|
|||
self.isUserInteracting = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func assertUserInteraction() {
|
||||
if let task = longIdleTask {
|
||||
task.cancel()
|
||||
|
@ -386,7 +385,7 @@ extension MenuStyle where Self == ToolbarMenuStyle {
|
|||
|
||||
struct VMToolbarView_Previews: PreviewProvider {
|
||||
@State static var state = VMWindowState()
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
VMToolbarView(state: $state)
|
||||
}
|
||||
|
|
|
@ -21,55 +21,55 @@ struct VMWindowState: Identifiable {
|
|||
enum Device: Identifiable, Hashable {
|
||||
case display(CSDisplay, Int)
|
||||
case serial(CSPort, Int)
|
||||
|
||||
|
||||
var configIndex: Int {
|
||||
switch self {
|
||||
case .display(_, let index): return index
|
||||
case .serial(_, let index): return index
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var id: Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let id = UUID()
|
||||
|
||||
|
||||
var device: Device?
|
||||
|
||||
|
||||
private var shouldViewportChange: Bool {
|
||||
!(displayScale == 1.0 && displayOrigin == .zero)
|
||||
}
|
||||
|
||||
|
||||
var displayScale: CGFloat = 1.0 {
|
||||
didSet {
|
||||
isViewportChanged = shouldViewportChange
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var displayOrigin: CGPoint = CGPoint(x: 0, y: 0) {
|
||||
didSet {
|
||||
isViewportChanged = shouldViewportChange
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var displayViewSize: CGSize = .zero
|
||||
|
||||
|
||||
var isDisplayZoomLocked: Bool = false
|
||||
|
||||
|
||||
var isKeyboardRequested: Bool = false
|
||||
|
||||
|
||||
var isKeyboardShown: Bool = false
|
||||
|
||||
|
||||
var isViewportChanged: Bool = false
|
||||
|
||||
|
||||
var isUserInteracting: Bool = false
|
||||
|
||||
|
||||
var isBusy: Bool = false
|
||||
|
||||
|
||||
var isRunning: Bool = false
|
||||
|
||||
|
||||
var alert: Alert?
|
||||
}
|
||||
|
||||
|
@ -83,14 +83,14 @@ extension VMWindowState {
|
|||
case .terminateApp: return 1
|
||||
case .restart: return 2
|
||||
#if !WITH_QEMU_TCI
|
||||
case .deviceConnected(_): return 3
|
||||
case .deviceConnected: return 3
|
||||
#endif
|
||||
case .nonfatalError(_): return 4
|
||||
case .fatalError(_): return 5
|
||||
case .nonfatalError: return 4
|
||||
case .fatalError: return 5
|
||||
case .memoryWarning: return 6
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case powerDown
|
||||
case terminateApp
|
||||
case restart
|
||||
|
@ -109,7 +109,7 @@ extension VMWindowState {
|
|||
private var kVMDefaultResizeCmd: String {
|
||||
"stty cols $COLS rows $ROWS\\n"
|
||||
}
|
||||
|
||||
|
||||
mutating func resizeDisplayToFit(_ display: CSDisplay, size: CGSize = .zero) {
|
||||
let viewSize = displayViewSize
|
||||
let displaySize = size == .zero ? display.displaySize : size
|
||||
|
@ -119,13 +119,13 @@ extension VMWindowState {
|
|||
displayScale = viewportScale
|
||||
displayOrigin = .zero
|
||||
}
|
||||
|
||||
|
||||
private mutating func resetDisplay(_ display: CSDisplay) {
|
||||
// persist this change in viewState
|
||||
displayScale = 1.0
|
||||
displayOrigin = .zero
|
||||
}
|
||||
|
||||
|
||||
private mutating func resetConsole(_ serial: CSPort, command: String? = nil) {
|
||||
let cols = Int(displayViewSize.width)
|
||||
let rows = Int(displayViewSize.height)
|
||||
|
@ -136,7 +136,7 @@ extension VMWindowState {
|
|||
.replacingOccurrences(of: "\\n", with: "\n")
|
||||
serial.write(cmd.data(using: .nonLossyASCII)!)
|
||||
}
|
||||
|
||||
|
||||
mutating func toggleDisplayResize(command: String? = nil) {
|
||||
if case let .display(display, _) = device {
|
||||
if isViewportChanged {
|
||||
|
@ -170,7 +170,7 @@ extension VMWindowState {
|
|||
window.isKeyboardVisible = isKeyboardShown
|
||||
registryEntry.windowSettings[id] = window
|
||||
}
|
||||
|
||||
|
||||
mutating func restoreWindow(from registryEntry: UTMRegistryEntry, device: Device?) {
|
||||
guard case let .display(_, id) = device else {
|
||||
return
|
||||
|
|
|
@ -22,11 +22,11 @@ struct VMWindowView: View {
|
|||
@State private var state = VMWindowState()
|
||||
@EnvironmentObject private var session: VMSessionState
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
|
||||
|
||||
private let keyboardDidShowNotification = NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)
|
||||
private let keyboardDidHideNotification = NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)
|
||||
private let didReceiveMemoryWarningNotification = NotificationCenter.default.publisher(for: UIApplication.didReceiveMemoryWarningNotification)
|
||||
|
||||
|
||||
private func withOptionalAnimation<Result>(_ animation: Animation? = .default, _ body: () throws -> Result) rethrows -> Result {
|
||||
if UIAccessibility.isReduceMotionEnabled {
|
||||
return try body()
|
||||
|
@ -34,19 +34,19 @@ struct VMWindowView: View {
|
|||
return try withAnimation(animation, body)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
ZStack {
|
||||
if let device = state.device {
|
||||
switch device {
|
||||
case .display(_, _):
|
||||
case .display:
|
||||
VMDisplayHostedView(vm: session.vm, device: device, state: $state)
|
||||
case .serial(_, _):
|
||||
case .serial:
|
||||
VMDisplayHostedView(vm: session.vm, device: device, state: $state)
|
||||
}
|
||||
} else if !state.isBusy && state.isRunning {
|
||||
// headless
|
||||
// Headless
|
||||
HeadlessView()
|
||||
}
|
||||
if state.isBusy || !state.isRunning {
|
||||
|
@ -117,7 +117,7 @@ struct VMWindowView: View {
|
|||
#endif
|
||||
case .nonfatalError(let message), .fatalError(let message):
|
||||
return Alert(title: Text(message), dismissButton: .cancel(Text("OK")) {
|
||||
if case .fatalError(_) = type {
|
||||
if case .fatalError = type {
|
||||
session.stop()
|
||||
} else {
|
||||
session.nonfatalError = nil
|
||||
|
@ -202,7 +202,7 @@ struct VMWindowView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func vmStateUpdated(from oldState: UTMVirtualMachineState?, to vmState: UTMVirtualMachineState) {
|
||||
if oldState == .started {
|
||||
saveWindow()
|
||||
|
@ -260,7 +260,7 @@ private struct HeadlessView: View {
|
|||
|
||||
#if !os(visionOS)
|
||||
/// Stub for non-Vision platforms
|
||||
fileprivate struct VMToolbarOrnamentModifier: ViewModifier {
|
||||
private struct VMToolbarOrnamentModifier: ViewModifier {
|
||||
@Binding var state: VMWindowState
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
|
|
|
@ -19,7 +19,7 @@ import SwiftUI
|
|||
struct VMWizardView: View {
|
||||
@StateObject var wizardState = VMWizardState()
|
||||
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
|
||||
|
||||
|
||||
var body: some View {
|
||||
if #available(iOS 16, visionOS 1.0, *) {
|
||||
WizardNavigationView(wizardState: wizardState) {
|
||||
|
@ -39,7 +39,7 @@ struct VMWizardView: View {
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate struct WizardToolbar: ViewModifier {
|
||||
private struct WizardToolbar: ViewModifier {
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
let onDismiss: () -> Void
|
||||
@EnvironmentObject private var data: UTMData
|
||||
|
@ -87,13 +87,13 @@ fileprivate struct WizardToolbar: ViewModifier {
|
|||
|
||||
@available(iOS, deprecated: 17, message: "Use WizardViewWrapper")
|
||||
@available(visionOS, deprecated: 1, message: "Use WizardViewWrapper")
|
||||
fileprivate struct WizardWrapper: View {
|
||||
private struct WizardWrapper: View {
|
||||
let page: VMWizardPage
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
@State private var nextPage: VMWizardPage?
|
||||
let onDismiss: () -> Void
|
||||
@EnvironmentObject private var data: UTMData
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
WizardViewWrapper(page: page, wizardState: wizardState)
|
||||
|
@ -125,14 +125,14 @@ fileprivate struct WizardWrapper: View {
|
|||
}
|
||||
|
||||
@available(iOS 16, visionOS 1.0, *)
|
||||
fileprivate struct WizardNavigationView: View {
|
||||
private struct WizardNavigationView: View {
|
||||
@StateObject var wizardState = VMWizardState()
|
||||
let onDismiss: () -> Void
|
||||
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
|
||||
@State private var navigationPath: NavigationPath = .init()
|
||||
@State private var previousPage: VMWizardPage?
|
||||
@State private var isAlertShown: Bool = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationStack(path: $wizardState.pageHistory) {
|
||||
WizardViewWrapper(page: .start, wizardState: wizardState)
|
||||
|
@ -157,7 +157,7 @@ fileprivate struct WizardNavigationView: View {
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate struct WizardViewWrapper: View {
|
||||
private struct WizardViewWrapper: View {
|
||||
let page: VMWizardPage
|
||||
@ObservedObject var wizardState: VMWizardState
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@ import SwiftUI
|
|||
|
||||
@available(macOS 11, *)
|
||||
struct SettingsView: View {
|
||||
|
||||
var body: some View {
|
||||
TabView {
|
||||
ApplicationSettingsView().padding()
|
||||
|
@ -47,7 +46,7 @@ struct ApplicationSettingsView: View {
|
|||
@AppStorage("ShowMenuIcon") var isMenuIconShown = false
|
||||
@AppStorage("PreventIdleSleep") var isPreventIdleSleep = false
|
||||
@AppStorage("NoQuitConfirmation") var isNoQuitConfirmation = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Toggle(isOn: $isKeepRunningAfterLastWindowClosed, label: {
|
||||
|
@ -81,7 +80,7 @@ struct DisplaySettingsView: View {
|
|||
@AppStorage("NoSaveScreenshot") var isNoSaveScreenshot = false
|
||||
@AppStorage("QEMURendererBackend") var qemuRendererBackend: UTMQEMURendererBackend = .qemuRendererBackendDefault
|
||||
@AppStorage("QEMURendererFPSLimit") var qemuRendererFpsLimit: Int = 0
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("Display")) {
|
||||
|
@ -92,7 +91,7 @@ struct DisplaySettingsView: View {
|
|||
Text("Do not save VM screenshot to disk")
|
||||
}.help("If enabled, any existing screenshot will be deleted the next time the VM is started.")
|
||||
}
|
||||
|
||||
|
||||
Section(header: Text("QEMU Graphics Acceleration")) {
|
||||
Picker("Renderer Backend", selection: $qemuRendererBackend) {
|
||||
Text("Default").tag(UTMQEMURendererBackend.qemuRendererBackendDefault)
|
||||
|
@ -113,7 +112,7 @@ struct DisplaySettingsView: View {
|
|||
|
||||
struct SoundSettingsView: View {
|
||||
@AppStorage("QEMUSoundBackend") var qemuSoundBackend: UTMQEMUSoundBackend = .qemuSoundBackendDefault
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("QEMU Sound")) {
|
||||
|
@ -136,7 +135,7 @@ struct InputSettingsView: View {
|
|||
@AppStorage("IsNumLockForced") var isNumLockForced = false
|
||||
@AppStorage("InvertScroll") var isInvertScroll = false
|
||||
@AppStorage("NoUsbPrompt") var isNoUsbPrompt = false
|
||||
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(header: Text("Mouse/Keyboard")) {
|
||||
|
@ -144,13 +143,13 @@ struct InputSettingsView: View {
|
|||
Text("Capture input automatically when entering full screen")
|
||||
}.help("If enabled, input capture will toggle automatically when entering and exiting full screen mode.")
|
||||
}
|
||||
|
||||
|
||||
Section(header: Text("Console")) {
|
||||
Toggle(isOn: $isOptionAsMetaKey, label: {
|
||||
Text("Option (⌥) is Meta key")
|
||||
}).help("If enabled, Option will be mapped to the Meta key which can be useful for emacs. Otherwise, option will work as the system intended (such as for entering international text).")
|
||||
}
|
||||
|
||||
|
||||
Section(header: Text("QEMU Pointer")) {
|
||||
Toggle(isOn: $isCtrlRightClick, label: {
|
||||
Text("Hold Control (⌃) for right click")
|
||||
|
@ -159,7 +158,7 @@ struct InputSettingsView: View {
|
|||
Text("Invert scrolling")
|
||||
}).help("If enabled, scroll wheel input will be inverted.")
|
||||
}
|
||||
|
||||
|
||||
Section(header: Text("QEMU Keyboard")) {
|
||||
Toggle(isOn: $isAlternativeCaptureKey, label: {
|
||||
Text("Use Command+Option (⌘+⌥) for input capture/release")
|
||||
|
@ -171,7 +170,7 @@ struct InputSettingsView: View {
|
|||
Text("Num Lock is forced on")
|
||||
}).help("If enabled, num lock will always be on to the guest. Note this may make your keyboard's num lock indicator out of sync.")
|
||||
}
|
||||
|
||||
|
||||
Section(header: Text("QEMU USB")) {
|
||||
Toggle(isOn: $isNoUsbPrompt, label: {
|
||||
Text("Do not show prompt when USB device is plugged in")
|
||||
|
|
|
@ -19,7 +19,7 @@ import SwiftUI
|
|||
struct UTMApp: App {
|
||||
@State var data = UTMData()
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate
|
||||
|
||||
|
||||
@ViewBuilder
|
||||
var homeWindow: some View {
|
||||
ContentView().environmentObject(data)
|
||||
|
@ -33,7 +33,7 @@ struct UTMApp: App {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SceneBuilder
|
||||
var oldBody: some Scene {
|
||||
WindowGroup {
|
||||
|
@ -59,7 +59,7 @@ struct UTMApp: App {
|
|||
}
|
||||
UTMMenuBarExtraScene(data: data)
|
||||
}
|
||||
|
||||
|
||||
// HACK: SwiftUI doesn't provide if-statement support in SceneBuilder
|
||||
var body: some Scene {
|
||||
if #available(macOS 13, *) {
|
||||
|
|
|
@ -21,7 +21,7 @@ struct VMWizardView: View {
|
|||
@StateObject var wizardState = VMWizardState()
|
||||
@Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
|
||||
@EnvironmentObject private var data: UTMData
|
||||
|
||||
|
||||
/// SwiftUI BUG: on macOS 12, when VoiceOver is enabled and isBusy changes
|
||||
/// the disable state of a button being clicked, the app crashes
|
||||
private var isNeverDisabledWorkaround: Bool {
|
||||
|
@ -36,7 +36,7 @@ struct VMWizardView: View {
|
|||
return true
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch wizardState.currentPage {
|
||||
|
|
|
@ -43,15 +43,15 @@ class UTMScriptingConfigImpl {
|
|||
private var bytesInMib: Int64 {
|
||||
1048576
|
||||
}
|
||||
|
||||
|
||||
private(set) var config: any UTMConfiguration
|
||||
private weak var data: UTMData?
|
||||
|
||||
|
||||
init(_ config: any UTMConfiguration, data: UTMData? = nil) {
|
||||
self.config = config
|
||||
self.data = data
|
||||
}
|
||||
|
||||
|
||||
func serializeConfiguration() -> [AnyHashable : Any] {
|
||||
if let qemuConfig = config as? UTMQemuConfiguration {
|
||||
return serializeQemuConfiguration(qemuConfig)
|
||||
|
@ -61,7 +61,7 @@ class UTMScriptingConfigImpl {
|
|||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func updateConfiguration(from record: [AnyHashable : Any]) throws {
|
||||
if let _ = config as? UTMQemuConfiguration {
|
||||
try updateQemuConfiguration(from: record)
|
||||
|
@ -71,7 +71,7 @@ class UTMScriptingConfigImpl {
|
|||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func size(of drive: any UTMConfigurationDrive) -> Int {
|
||||
guard let data = data else {
|
||||
return 0
|
||||
|
@ -92,7 +92,7 @@ extension UTMScriptingConfigImpl {
|
|||
case .virtfs: return .virtFS
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func serializeQemuConfiguration(_ config: UTMQemuConfiguration) -> [AnyHashable : Any] {
|
||||
[
|
||||
"name": config.information.name,
|
||||
|
@ -109,7 +109,7 @@ extension UTMScriptingConfigImpl {
|
|||
"serialPorts": config.serials.enumerated().map({ serializeQemuSerial($1, index: $0) }),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
private func qemuDriveInterface(from interface: QEMUDriveInterface) -> UTMScriptingQemuDriveInterface {
|
||||
switch interface {
|
||||
case .none: return .none
|
||||
|
|
|
@ -66,7 +66,7 @@ extension Optional where Wrapped == Bool {
|
|||
self = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var bound: Wrapped {
|
||||
get {
|
||||
return _bound ?? false
|
||||
|
@ -90,11 +90,9 @@ extension Binding where Value == Bool {
|
|||
extension LocalizedStringKey {
|
||||
var localizedString: String {
|
||||
let mirror = Mirror(reflecting: self)
|
||||
var key: String? = nil
|
||||
for property in mirror.children {
|
||||
if property.label == "key" {
|
||||
key = property.value as? String
|
||||
}
|
||||
var key: String?
|
||||
for property in mirror.children where property.label == "key" {
|
||||
key = property.value as? String
|
||||
}
|
||||
guard let goodKey = key else {
|
||||
logger.error("Failed to get localization key")
|
||||
|
@ -120,13 +118,11 @@ extension IndexSet: Identifiable {
|
|||
|
||||
extension Array {
|
||||
subscript(indicies: IndexSet) -> [Element] {
|
||||
get {
|
||||
var slice = [Element]()
|
||||
for i in indicies {
|
||||
slice.append(self[i])
|
||||
}
|
||||
return slice
|
||||
var slice = [Element]()
|
||||
for i in indicies {
|
||||
slice.append(self[i])
|
||||
}
|
||||
return slice
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,10 +139,10 @@ extension View {
|
|||
|
||||
extension UTType {
|
||||
static let UTM = UTType(exportedAs: "com.utmapp.utm")
|
||||
|
||||
|
||||
// SwiftUI BUG: exportedAs: "com.utmapp.utm" doesn't work on macOS and older iOS
|
||||
static let UTMextension = UTType(exportedAs: "utm")
|
||||
|
||||
|
||||
static let appleLog = UTType(filenameExtension: "log")!
|
||||
|
||||
static let ipsw = UTType(filenameExtension: "ipsw")!
|
||||
|
@ -164,20 +160,20 @@ extension Color {
|
|||
if hex.count != 7 { // The '#' included
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let hexColor = String(hex.dropFirst())
|
||||
|
||||
|
||||
let scanner = Scanner(string: hexColor)
|
||||
var hexNumber: UInt64 = 0
|
||||
|
||||
|
||||
if !scanner.scanHexInt64(&hexNumber) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
|
||||
let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
|
||||
let b = CGFloat(hexNumber & 0x0000ff) / 255
|
||||
|
||||
|
||||
self.init(.displayP3, red: r, green: g, blue: b, opacity: 1.0)
|
||||
}
|
||||
}
|
||||
|
@ -186,11 +182,11 @@ extension CGColor {
|
|||
var hexString: String? {
|
||||
hexString(for: .init(name: CGColorSpace.displayP3)!)
|
||||
}
|
||||
|
||||
|
||||
var sRGBhexString: String? {
|
||||
hexString(for: .init(name: CGColorSpace.sRGB)!)
|
||||
}
|
||||
|
||||
|
||||
private func hexString(for colorSpace: CGColorSpace) -> String? {
|
||||
guard let rgbColor = self.converted(to: colorSpace, intent: .defaultIntent, options: nil),
|
||||
let components = rgbColor.components else {
|
||||
|
@ -241,23 +237,23 @@ extension UIImage {
|
|||
if hex.count != 7 { // The '#' included
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let hexColor = String(hex.dropFirst())
|
||||
|
||||
|
||||
let scanner = Scanner(string: hexColor)
|
||||
var hexNumber: UInt64 = 0
|
||||
|
||||
|
||||
if !scanner.scanHexInt64(&hexNumber) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let r = CGFloat((hexNumber & 0xff0000) >> 16) / 255
|
||||
let g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255
|
||||
let b = CGFloat(hexNumber & 0x0000ff) / 255
|
||||
|
||||
|
||||
self.init(displayP3Red: r, green: g, blue: b, alpha: 1.0)
|
||||
}
|
||||
|
||||
|
||||
var sRGBhexString: String? {
|
||||
cgColor.sRGBhexString
|
||||
}
|
||||
|
@ -271,21 +267,19 @@ typealias PlatformImage = UIImage
|
|||
#endif
|
||||
|
||||
#if os(macOS)
|
||||
enum FakeKeyboardType : Int {
|
||||
enum FakeKeyboardType: Int {
|
||||
case asciiCapable
|
||||
case decimalPad
|
||||
case numberPad
|
||||
}
|
||||
|
||||
struct EditButton {
|
||||
|
||||
}
|
||||
struct EditButton {}
|
||||
|
||||
extension View {
|
||||
func keyboardType(_ type: FakeKeyboardType) -> some View {
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
func navigationBarItems(trailing: EditButton) -> some View {
|
||||
return self
|
||||
}
|
||||
|
@ -306,7 +300,7 @@ extension NSImage {
|
|||
struct Setting<T> {
|
||||
private(set) var keyName: String
|
||||
private var defaultValue: T
|
||||
|
||||
|
||||
var wrappedValue: T {
|
||||
get {
|
||||
let defaults = UserDefaults.standard
|
||||
|
@ -315,13 +309,13 @@ struct Setting<T> {
|
|||
}
|
||||
return value as! T
|
||||
}
|
||||
|
||||
|
||||
set {
|
||||
let defaults = UserDefaults.standard
|
||||
defaults.set(newValue, forKey: keyName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init(wrappedValue: T, _ keyName: String) {
|
||||
self.defaultValue = wrappedValue
|
||||
self.keyName = keyName
|
||||
|
@ -337,7 +331,7 @@ extension URL {
|
|||
return .withSecurityScope
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private static var defaultResolutionOptions: BookmarkResolutionOptions {
|
||||
#if os(iOS) || os(visionOS)
|
||||
return []
|
||||
|
@ -345,7 +339,7 @@ extension URL {
|
|||
return .withSecurityScope
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func persistentBookmarkData(isReadyOnly: Bool = false) throws -> Data {
|
||||
var options = Self.defaultCreationOptions
|
||||
#if os(macOS)
|
||||
|
@ -363,7 +357,7 @@ extension URL {
|
|||
includingResourceValuesForKeys: nil,
|
||||
relativeTo: nil)
|
||||
}
|
||||
|
||||
|
||||
init(resolvingPersistentBookmarkData bookmark: Data) throws {
|
||||
var stale: Bool = false
|
||||
try self.init(resolvingBookmarkData: bookmark,
|
||||
|
|
|
@ -430,18 +430,18 @@ extension UTMRegistryEntry {
|
|||
try container.encodeIfPresent(remoteBookmark, forKey: .remoteBookmark)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Window: Codable, Equatable {
|
||||
var scale: CGFloat = 1.0
|
||||
|
||||
|
||||
var origin: CGPoint = .zero
|
||||
|
||||
|
||||
var isToolbarVisible: Bool = true
|
||||
|
||||
|
||||
var isKeyboardVisible: Bool = false
|
||||
|
||||
|
||||
var isDisplayZoomLocked: Bool = true
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case scale = "Scale"
|
||||
case origin = "Origin"
|
||||
|
@ -449,10 +449,10 @@ extension UTMRegistryEntry {
|
|||
case isKeyboardVisible = "KeyboardVisible"
|
||||
case isDisplayZoomLocked = "DisplayZoomLocked"
|
||||
}
|
||||
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
scale = try container.decode(CGFloat.self, forKey: .scale)
|
||||
|
@ -461,7 +461,7 @@ extension UTMRegistryEntry {
|
|||
isKeyboardVisible = try container.decode(Bool.self, forKey: .isKeyboardVisible)
|
||||
isDisplayZoomLocked = try container.decode(Bool.self, forKey: .isDisplayZoomLocked)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(scale, forKey: .scale)
|
||||
|
@ -471,28 +471,28 @@ extension UTMRegistryEntry {
|
|||
try container.encode(isDisplayZoomLocked, forKey: .isDisplayZoomLocked)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Terminal: Codable, Equatable {
|
||||
var columns: Int
|
||||
|
||||
|
||||
var rows: Int
|
||||
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case columns = "Columns"
|
||||
case rows = "Rows"
|
||||
}
|
||||
|
||||
|
||||
init(columns: Int = 80, rows: Int = 24) {
|
||||
self.columns = columns
|
||||
self.rows = rows
|
||||
}
|
||||
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
columns = try container.decode(Int.self, forKey: .columns)
|
||||
rows = try container.decode(Int.self, forKey: .rows)
|
||||
}
|
||||
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(columns, forKey: .columns)
|
||||
|
|
Loading…
Reference in New Issue