VMConfigQEMUView.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. //
  2. // Copyright © 2020 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 SwiftUI
  17. @available(iOS 14, macOS 11, *)
  18. struct VMConfigQEMUView: View {
  19. @ObservedObject var config: UTMQemuConfiguration
  20. @State private var newArg: String = ""
  21. @State private var showExportLog: Bool = false
  22. @State private var showExportArgs: Bool = false
  23. @EnvironmentObject private var data: UTMData
  24. private var logExists: Bool {
  25. guard let path = config.existingPath else {
  26. return false
  27. }
  28. let logPath = path.appendingPathComponent(UTMQemuConfiguration.debugLogName())
  29. return FileManager.default.fileExists(atPath: logPath.path)
  30. }
  31. var body: some View {
  32. VStack {
  33. Form {
  34. Section(header: Text("Logging")) {
  35. Toggle(isOn: $config.debugLogEnabled, label: {
  36. Text("Debug Logging")
  37. })
  38. Button("Export Debug Log") {
  39. showExportLog.toggle()
  40. }.modifier(VMShareItemModifier(isPresented: $showExportLog, items: exportDebugLog))
  41. .disabled(!logExists)
  42. }
  43. Section(header: Text("QEMU Arguments")) {
  44. Button("Export QEMU Command") {
  45. showExportArgs.toggle()
  46. }.modifier(VMShareItemModifier(isPresented: $showExportArgs, items: exportArgs))
  47. Toggle(isOn: $config.ignoreAllConfiguration.animation(), label: {
  48. Text("Advanced: Bypass configuration and manually specify arguments")
  49. })
  50. let qemuSystem = UTMQemuSystem(configuration: config, imgPath: URL(fileURLWithPath: "Images"))
  51. let fixedArgs = qemuSystem.argv
  52. #if os(macOS)
  53. VStack {
  54. ForEach(fixedArgs, id: \.self) { arg in
  55. TextField("", text: .constant(arg))
  56. }.disabled(true)
  57. CustomArguments(config: config)
  58. TextField("New...", text: $newArg, onEditingChanged: addArg)
  59. }
  60. #else
  61. List {
  62. ForEach(fixedArgs, id: \.self) { arg in
  63. Text(arg)
  64. }.foregroundColor(.secondary)
  65. CustomArguments(config: config)
  66. TextField("New...", text: $newArg, onEditingChanged: addArg)
  67. }
  68. #endif
  69. }
  70. }.navigationBarItems(trailing: EditButton())
  71. .disableAutocorrection(true)
  72. }
  73. }
  74. private func exportDebugLog() -> [URL] {
  75. if let result = try? data.exportDebugLog(forConfig: config) {
  76. return result
  77. } else {
  78. return [] // TODO: implement error handling
  79. }
  80. }
  81. private func deleteArg(offsets: IndexSet) {
  82. for offset in offsets {
  83. config.removeArgument(at: offset)
  84. }
  85. }
  86. private func moveArg(source: IndexSet, destination: Int) {
  87. for offset in source {
  88. config.moveArgumentIndex(offset, to: destination)
  89. }
  90. }
  91. private func addArg(editing: Bool) {
  92. guard !editing else {
  93. return
  94. }
  95. if newArg != "" {
  96. config.newArgument(newArg)
  97. }
  98. newArg = ""
  99. }
  100. private func exportArgs() -> [String] {
  101. let existingPath = config.existingPath ?? URL(fileURLWithPath: "Images")
  102. let qemuSystem = UTMQemuSystem(configuration: config, imgPath: existingPath)
  103. qemuSystem.updateArgv(withUserOptions: true)
  104. var argString = "qemu-system-\(config.systemArchitecture ?? "unknown")"
  105. for arg in qemuSystem.argv {
  106. if arg.contains(" ") {
  107. argString += " \"\(arg)\""
  108. } else {
  109. argString += " \(arg)"
  110. }
  111. }
  112. return [argString]
  113. }
  114. }
  115. @available(iOS 14, macOS 11, *)
  116. struct CustomArguments: View {
  117. @ObservedObject var config: UTMQemuConfiguration
  118. var body: some View {
  119. ForEach(0..<config.countArguments, id: \.self) { i in
  120. let argBinding = Binding<String> {
  121. if i < config.countArguments {
  122. return config.argument(for: i) ?? ""
  123. } else {
  124. // WA for a SwiftUI bug on macOS that uses old countArguments
  125. return ""
  126. }
  127. } set: {
  128. config.updateArgument(at: i, withValue: $0)
  129. }
  130. HStack {
  131. TextField("Argument", text: argBinding, onEditingChanged: { editing in
  132. if !editing && argBinding.wrappedValue == "" {
  133. config.removeArgument(at: i)
  134. }
  135. })
  136. #if os(macOS)
  137. Spacer()
  138. if i != 0 {
  139. Button(action: { config.moveArgumentIndex(i, to: i-1) }, label: {
  140. Label("Move Up", systemImage: "arrow.up").labelStyle(IconOnlyLabelStyle())
  141. })
  142. }
  143. #endif
  144. }
  145. }.onDelete(perform: deleteArg)
  146. .onMove(perform: moveArg)
  147. }
  148. private func deleteArg(offsets: IndexSet) {
  149. for offset in offsets {
  150. config.removeArgument(at: offset)
  151. }
  152. }
  153. private func moveArg(source: IndexSet, destination: Int) {
  154. for offset in source {
  155. config.moveArgumentIndex(offset, to: destination)
  156. }
  157. }
  158. }
  159. @available(iOS 14, macOS 11, *)
  160. struct VMConfigQEMUView_Previews: PreviewProvider {
  161. @ObservedObject static private var config = UTMQemuConfiguration()
  162. static var previews: some View {
  163. VMConfigQEMUView(config: config)
  164. }
  165. }