UTMScriptingVirtualMachineImpl.swift 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // Copyright © 2022 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(UTMScriptingVirtualMachineImpl)
  19. class UTMScriptingVirtualMachineImpl: NSObject {
  20. private var vm: UTMVirtualMachine
  21. private var data: UTMData
  22. @objc var id: String {
  23. vm.id.uuidString
  24. }
  25. @objc var name: String {
  26. vm.detailsTitleLabel
  27. }
  28. @objc var notes: String {
  29. vm.detailsNotes ?? ""
  30. }
  31. @objc var machine: String {
  32. vm.detailsSystemTargetLabel
  33. }
  34. @objc var architecture: String {
  35. vm.detailsSystemArchitectureLabel
  36. }
  37. @objc var memory: String {
  38. vm.detailsSystemMemoryLabel
  39. }
  40. @objc var backend: UTMScriptingBackend {
  41. if vm is UTMQemuVirtualMachine {
  42. return .qemu
  43. } else if vm is UTMAppleVirtualMachine {
  44. return .apple
  45. } else {
  46. return .unavailable
  47. }
  48. }
  49. @objc var status: UTMScriptingStatus {
  50. switch vm.state {
  51. case .vmStopped: return .stopped
  52. case .vmStarting: return .starting
  53. case .vmStarted: return .started
  54. case .vmPausing: return .pausing
  55. case .vmPaused: return .paused
  56. case .vmResuming: return .resuming
  57. case .vmStopping: return .stopping
  58. @unknown default: return .stopped
  59. }
  60. }
  61. @objc var serialPorts: [UTMScriptingSerialPortImpl] {
  62. if let config = vm.config.qemuConfig {
  63. return config.serials.indices.map({ UTMScriptingSerialPortImpl(qemuSerial: config.serials[$0], parent: self, index: $0) })
  64. } else if let config = vm.config.appleConfig {
  65. return config.serials.indices.map({ UTMScriptingSerialPortImpl(appleSerial: config.serials[$0], parent: self, index: $0) })
  66. } else {
  67. return []
  68. }
  69. }
  70. override var objectSpecifier: NSScriptObjectSpecifier? {
  71. let appDescription = NSApplication.classDescription() as! NSScriptClassDescription
  72. return NSUniqueIDSpecifier(containerClassDescription: appDescription,
  73. containerSpecifier: nil,
  74. key: "scriptingVirtualMachines",
  75. uniqueID: id)
  76. }
  77. init(for vm: UTMVirtualMachine, data: UTMData) {
  78. self.vm = vm
  79. self.data = data
  80. }
  81. private func withScriptCommand<Result>(_ command: NSScriptCommand, body: @MainActor @escaping () async throws -> Result) {
  82. guard command.evaluatedReceivers as? Self == self else {
  83. return
  84. }
  85. command.suspendExecution()
  86. // we need to run this in next event loop due to the need to return before calling resume
  87. DispatchQueue.main.async {
  88. Task {
  89. do {
  90. let result = try await body()
  91. await MainActor.run {
  92. if result is Void {
  93. command.resumeExecution(withResult: nil)
  94. } else {
  95. command.resumeExecution(withResult: result)
  96. }
  97. }
  98. } catch {
  99. await MainActor.run {
  100. command.scriptErrorNumber = errOSAGeneralError
  101. command.scriptErrorString = error.localizedDescription
  102. command.resumeExecution(withResult: nil)
  103. }
  104. }
  105. }
  106. }
  107. }
  108. @objc func start(_ command: NSScriptCommand) {
  109. let shouldSaveState = command.evaluatedArguments?["saveFlag"] as? Bool ?? true
  110. withScriptCommand(command) { [self] in
  111. if !shouldSaveState {
  112. guard let vm = vm as? UTMQemuVirtualMachine else {
  113. throw ScriptingError.operationNotSupported
  114. }
  115. vm.isRunningAsSnapshot = true
  116. }
  117. data.run(vm: vm, startImmediately: false)
  118. if vm.state == .vmStopped {
  119. try await vm.vmStart()
  120. } else if vm.state == .vmPaused {
  121. try await vm.vmResume()
  122. } else {
  123. throw ScriptingError.operationNotAvailable
  124. }
  125. }
  126. }
  127. @objc func suspend(_ command: NSScriptCommand) {
  128. let shouldSaveState = command.evaluatedArguments?["saveFlag"] as? Bool ?? false
  129. withScriptCommand(command) { [self] in
  130. try await vm.vmPause(save: shouldSaveState)
  131. }
  132. }
  133. @objc func stop(_ command: NSScriptCommand) {
  134. let stopMethod = command.evaluatedArguments?["stopBy"] as? UTMScriptingStopMethod ?? .force
  135. withScriptCommand(command) { [self] in
  136. switch stopMethod {
  137. case .force:
  138. try await vm.vmStop(force: false)
  139. case .kill:
  140. try await vm.vmStop(force: true)
  141. case .request:
  142. vm.requestGuestPowerDown()
  143. }
  144. }
  145. }
  146. }
  147. extension UTMScriptingVirtualMachineImpl {
  148. enum ScriptingError: Error, LocalizedError {
  149. case operationNotAvailable
  150. case operationNotSupported
  151. var localizedDescription: String {
  152. switch self {
  153. case .operationNotAvailable: return NSLocalizedString("Operation not available.", comment: "UTMScriptingVirtualMachineImpl")
  154. case .operationNotSupported: return NSLocalizedString("Operation not supported by the backend.", comment: "UTMScriptingVirtualMachineImpl")
  155. }
  156. }
  157. }
  158. }