2
0

VMConfigQEMUArgumentsView.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  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 SwiftUI
  17. @available(macOS 12, *)
  18. struct VMConfigQEMUArgumentsView: View {
  19. @Binding var config: UTMQemuConfigurationQEMU
  20. let architecture: QEMUArchitecture
  21. let fixedArguments: [QEMUArgument]
  22. private let fixedUuids: Set<UUID>
  23. @State private var selected: Set<UUID>
  24. @State private var selectedArgument = QEMUArgument("")
  25. @FocusState private var focused: UUID?
  26. @State private var showExportArgs: Bool = false
  27. private var customUuids: Set<UUID> {
  28. Set(config.additionalArguments.map({ $0.id }))
  29. }
  30. private var exportShareItem: VMShareItemModifier.ShareItem {
  31. var argString = "qemu-system-\(architecture.rawValue)"
  32. for arg in fixedArguments {
  33. if arg.string.contains(" ") {
  34. argString += " \"\(arg.string)\""
  35. } else {
  36. argString += " \(arg.string)"
  37. }
  38. }
  39. for arg in config.additionalArguments {
  40. argString += " \(arg.string)"
  41. }
  42. return .qemuCommand(argString)
  43. }
  44. init(config: Binding<UTMQemuConfigurationQEMU>, architecture: QEMUArchitecture, fixedArguments: [QEMUArgument]) {
  45. self._config = config
  46. self.architecture = architecture
  47. self.fixedArguments = fixedArguments
  48. self.fixedUuids = Set(fixedArguments.map({ $0.id }))
  49. self._selected = State<Set<UUID>>(initialValue: .init())
  50. }
  51. var body: some View {
  52. VStack {
  53. Table(of: QEMUArgument.self, selection: $selected) {
  54. TableColumn("Arguments") { arg in
  55. let customSelected = selected.intersection(customUuids)
  56. if fixedUuids.contains(arg.id) || customSelected.count > 1 || !customSelected.contains(arg.id) {
  57. Text(arg.string)
  58. .foregroundColor(fixedUuids.contains(arg.id) ? .secondary : .primary)
  59. .textSelection(.enabled)
  60. } else {
  61. TextField("", text: $selectedArgument.string)
  62. .focused($focused, equals: arg.id)
  63. .onSubmit(of: .text) {
  64. if let index = config.additionalArguments.firstIndex(of: arg) {
  65. config.additionalArguments[index] = selectedArgument
  66. }
  67. }
  68. }
  69. }
  70. } rows: {
  71. ForEach(fixedArguments) { arg in
  72. TableRow(arg)
  73. }
  74. ForEach(config.additionalArguments) { arg in
  75. TableRow(arg)
  76. }
  77. }.onChange(of: selected) { newValue in
  78. // save changes to last selected argument
  79. if let index = config.additionalArguments.firstIndex(where: { $0.id == selectedArgument.id }) {
  80. config.additionalArguments[index] = selectedArgument
  81. selectedArgument = .init("")
  82. }
  83. // get new selected argument
  84. if let selectedId = selected.intersection(customUuids).first {
  85. if let arg = config.additionalArguments.first(where: { $0.id == selectedId }) {
  86. selectedArgument = arg
  87. }
  88. }
  89. }
  90. Spacer()
  91. HStack {
  92. Button {
  93. showExportArgs.toggle()
  94. } label: {
  95. Text("Export QEMU Command…")
  96. }.help("Export all arguments as a text file. This is only for debugging purposes as UTM's built-in QEMU differs from upstream QEMU in supported arguments.")
  97. Spacer()
  98. let customSelected = selected.intersection(customUuids)
  99. if !customSelected.isEmpty {
  100. if customSelected.count > 1 || customSelected.first != config.additionalArguments.first?.id {
  101. Button {
  102. for i in 1..<config.additionalArguments.count {
  103. if customSelected.contains(config.additionalArguments[i].id) {
  104. config.additionalArguments.move(fromOffsets: .init(integer: i), toOffset: i - 1)
  105. }
  106. }
  107. } label: {
  108. Text("Move Up")
  109. }
  110. }
  111. if customSelected.count > 1 || customSelected.first != config.additionalArguments.last?.id {
  112. Button {
  113. for i in (0..<config.additionalArguments.count-1).reversed() {
  114. if customSelected.contains(config.additionalArguments[i].id) {
  115. config.additionalArguments.move(fromOffsets: .init(integer: i), toOffset: i + 2)
  116. }
  117. }
  118. } label: {
  119. Text("Move Down")
  120. }
  121. }
  122. Button(role: .destructive) {
  123. config.additionalArguments.removeAll(where: { customSelected.contains($0.id) })
  124. } label: {
  125. Text("Delete")
  126. }
  127. }
  128. Button {
  129. let new = QEMUArgument("")
  130. config.additionalArguments.append(new)
  131. selected.removeAll()
  132. selected.insert(new.id)
  133. focused = new.id
  134. } label: {
  135. Text("New…")
  136. }
  137. }.padding([.bottom, .leading, .trailing])
  138. }.modifier(VMShareItemModifier(isPresented: $showExportArgs, shareItem: exportShareItem))
  139. }
  140. }