UTMScriptingAppDelegate.swift 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. //
  2. // Copyright © 2023 osy. All rights reserved.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. //
  16. import Foundation
  17. @MainActor
  18. @objc extension AppDelegate: UTMScriptable {
  19. private var bytesInMib: Int {
  20. 1048576
  21. }
  22. private var bytesInGib: Int {
  23. 1073741824
  24. }
  25. @objc func handleCreateCommand(_ command: NSCreateCommand) {
  26. if command.createClassDescription.implementationClassName == "UTMScriptingVirtualMachineImpl" {
  27. let properties = command.resolvedKeyDictionary
  28. withScriptCommand(command) { [self] in
  29. guard let backend = properties["backend"] as? AEKeyword, let backend = UTMScriptingBackend(rawValue: backend) else {
  30. throw ScriptingError.backendNotFound
  31. }
  32. guard let configuration = properties["configuration"] as? [AnyHashable : Any] else {
  33. throw ScriptingError.configurationNotFound
  34. }
  35. if backend == .qemu {
  36. return try await createQemuVirtualMachine(from: configuration).objectSpecifier
  37. } else if backend == .apple {
  38. return try await createAppleVirtualMachine(from: configuration).objectSpecifier
  39. } else {
  40. throw ScriptingError.backendNotFound
  41. }
  42. }
  43. } else {
  44. command.performDefaultImplementation()
  45. }
  46. }
  47. private func createQemuVirtualMachine(from record: [AnyHashable : Any]) async throws -> UTMScriptingVirtualMachineImpl {
  48. guard let data = data else {
  49. throw ScriptingError.notReady
  50. }
  51. guard record["name"] as? String != nil else {
  52. throw ScriptingError.nameNotSpecified
  53. }
  54. guard let architecture = record["architecture"] as? String, let architecture = QEMUArchitecture(rawValue: architecture) else {
  55. throw ScriptingError.architectureNotSpecified
  56. }
  57. let machine = record["machine"] as? String
  58. let target = architecture.targetType.init(rawValue: machine ?? "") ?? architecture.targetType.default
  59. let config = UTMQemuConfiguration()
  60. config.system.architecture = architecture
  61. config.system.target = target
  62. config.reset(forArchitecture: architecture, target: target)
  63. config.qemu.hasHypervisor = true
  64. config.qemu.hasUefiBoot = true
  65. // add default drives
  66. config.drives.append(UTMQemuConfigurationDrive(forArchitecture: architecture, target: target, isExternal: true))
  67. var fixed = UTMQemuConfigurationDrive(forArchitecture: architecture, target: target)
  68. fixed.sizeMib = 64 * bytesInGib / bytesInMib
  69. config.drives.append(fixed)
  70. // add a default serial device
  71. var serial = UTMQemuConfigurationSerial()
  72. serial.mode = .ptty
  73. config.serials = [serial]
  74. // remove GUI devices
  75. config.displays = []
  76. config.sound = []
  77. // parse the remaining config
  78. let wrapper = UTMScriptingConfigImpl(config)
  79. try wrapper.updateConfiguration(from: record)
  80. // create the vm
  81. let vm = try await data.create(config: config)
  82. return UTMScriptingVirtualMachineImpl(for: vm, data: data)
  83. }
  84. private func createAppleVirtualMachine(from record: [AnyHashable : Any]) async throws -> UTMScriptingVirtualMachineImpl {
  85. guard let data = data else {
  86. throw ScriptingError.notReady
  87. }
  88. guard #available(macOS 13, *) else {
  89. throw ScriptingError.backendNotSupported
  90. }
  91. guard record["name"] as? String != nil else {
  92. throw ScriptingError.nameNotSpecified
  93. }
  94. let config = UTMAppleConfiguration()
  95. config.system.boot = try UTMAppleConfigurationBoot(for: .linux)
  96. config.virtualization.hasBalloon = true
  97. config.virtualization.hasEntropy = true
  98. config.networks = [UTMAppleConfigurationNetwork()]
  99. // remove any display devices
  100. config.displays = []
  101. // add a default serial device
  102. var serial = UTMAppleConfigurationSerial()
  103. serial.mode = .ptty
  104. config.serials = [serial]
  105. // add default drives
  106. config.drives.append(UTMAppleConfigurationDrive(existingURL: nil, isExternal: true))
  107. config.drives.append(UTMAppleConfigurationDrive(newSize: 64 * bytesInGib / bytesInMib))
  108. // parse the remaining config
  109. let wrapper = UTMScriptingConfigImpl(config)
  110. try wrapper.updateConfiguration(from: record)
  111. // create the vm
  112. let vm = try await data.create(config: config)
  113. return UTMScriptingVirtualMachineImpl(for: vm, data: data)
  114. }
  115. enum ScriptingError: Error, LocalizedError {
  116. case notReady
  117. case backendNotFound
  118. case backendNotSupported
  119. case configurationNotFound
  120. case nameNotSpecified
  121. case architectureNotSpecified
  122. var errorDescription: String? {
  123. switch self {
  124. case .notReady: return NSLocalizedString("UTM is not ready to accept commands.", comment: "UTMScriptingAppDelegate")
  125. case .backendNotFound: return NSLocalizedString("A valid backend must be specified.", comment: "UTMScriptingAppDelegate")
  126. case .backendNotSupported: return NSLocalizedString("This backend is not supported on your machine.", comment: "UTMScriptingAppDelegate")
  127. case .configurationNotFound: return NSLocalizedString("A valid configuration must be specified.", comment: "UTMScriptingAppDelegate")
  128. case .nameNotSpecified: return NSLocalizedString("No name specified in the configuration.", comment: "UTMScriptingAppDelegate")
  129. case .architectureNotSpecified: return NSLocalizedString("No architecture specified in the configuration.", comment: "UTMScriptingAppDelegate")
  130. }
  131. }
  132. }
  133. }