Resolve more violations

This commit is contained in:
Isaac Marovitz 2023-09-08 21:40:00 -04:00
parent 46b9acf727
commit e9973129f8
No known key found for this signature in database
GPG Key ID: 97250B2B09A132E1
37 changed files with 369 additions and 386 deletions

View File

@ -2,6 +2,8 @@ disabled_rules:
- identifier_name
- type_name
- file_length
- type_body_length
- line_length
excluded:
- .build

View File

@ -10272,5 +10272,4 @@ extension QEMUArchitecture {
case .xtensaeb: return QEMUSerialDevice_xtensaeb.self
}
}
}

View File

@ -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
}

View File

@ -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:

View File

@ -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,

View File

@ -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

View File

@ -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()

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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"

View File

@ -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

View File

@ -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) {
}
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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, *) {

View File

@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -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)