VMContextMenuModifier.swift 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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. struct VMContextMenuModifier: ViewModifier {
  18. @ObservedObject var vm: VMData
  19. @EnvironmentObject private var data: UTMData
  20. @State private var showSharePopup = false
  21. @State private var confirmAction: ConfirmAction?
  22. @State private var shareItem: VMShareItemModifier.ShareItem?
  23. func body(content: Content) -> some View {
  24. #if os(macOS)
  25. if #unavailable(macOS 12) {
  26. bodyBigSur(content: content)
  27. } else {
  28. bodyFull(content: content)
  29. }
  30. #else
  31. return bodyFull(content: content)
  32. #endif
  33. }
  34. #if os(macOS)
  35. @ViewBuilder func bodyBigSur(content: Content) -> some View {
  36. content.contextMenu {
  37. Button {
  38. NSWorkspace.shared.activateFileViewerSelecting([vm.pathUrl])
  39. } label: {
  40. Label("Show in Finder", systemImage: "folder")
  41. }
  42. }
  43. }
  44. #endif
  45. /// Full context menu for anything other than Big Sur
  46. /// - Parameter content: Content
  47. /// - Returns: View
  48. @available(macOS 12, *)
  49. @ViewBuilder func bodyFull(content: Content) -> some View {
  50. content.contextMenu {
  51. #if os(macOS)
  52. Button {
  53. NSWorkspace.shared.activateFileViewerSelecting([vm.pathUrl])
  54. } label: {
  55. Label("Show in Finder", systemImage: "folder")
  56. }.help("Reveal where the VM is stored.")
  57. Divider()
  58. #endif
  59. #if !WITH_REMOTE // FIXME: implement remote feature
  60. Button {
  61. data.close(vm: vm) // close window
  62. data.edit(vm: vm)
  63. } label: {
  64. Label("Edit", systemImage: "slider.horizontal.3")
  65. }.disabled(vm.hasSuspendState || !vm.isModifyAllowed)
  66. .help("Modify settings for this VM.")
  67. #endif
  68. if vm.hasSuspendState || !vm.isStopped {
  69. Button {
  70. confirmAction = .confirmStopVM
  71. } label: {
  72. Label("Stop", systemImage: "stop")
  73. }.help("Stop the running VM.")
  74. } else if !vm.isModifyAllowed { // paused
  75. Button {
  76. data.run(vm: vm)
  77. } label: {
  78. Label("Resume", systemImage: "playpause")
  79. }.help("Resume running VM.")
  80. } else {
  81. Divider()
  82. Button {
  83. data.run(vm: vm)
  84. } label: {
  85. Label("Run", systemImage: "play")
  86. }.help("Run the VM in the foreground.")
  87. #if os(macOS) && arch(arm64)
  88. if #available(macOS 13, *), let appleConfig = vm.config as? UTMAppleConfiguration, appleConfig.system.boot.operatingSystem == .macOS {
  89. Button {
  90. data.run(vm: vm, options: .bootRecovery)
  91. } label: {
  92. Label("Run Recovery", systemImage: "lifepreserver.fill")
  93. }.help("Boot into recovery mode.")
  94. }
  95. #endif
  96. if let _ = vm.config as? UTMQemuConfiguration {
  97. Button {
  98. data.run(vm: vm, options: .bootDisposibleMode)
  99. } label: {
  100. Label("Run without saving changes", systemImage: "memories")
  101. }.help("Run the VM in the foreground, without saving data changes to disk.")
  102. }
  103. #if os(iOS) || os(visionOS)
  104. if let qemuConfig = vm.config as? UTMQemuConfiguration {
  105. Button {
  106. NotificationCenter.default.post(name: NSNotification.InstallGuestTools, object: vm.wrapped!)
  107. } label: {
  108. Label("Install Windows Guest Tools…", systemImage: "wrench.and.screwdriver")
  109. }.help("Download and mount the guest tools for Windows.")
  110. }
  111. #endif
  112. Divider()
  113. }
  114. #if !WITH_REMOTE // FIXME: implement remote feature
  115. Button {
  116. shareItem = .utmCopy(vm)
  117. showSharePopup.toggle()
  118. } label: {
  119. Label("Share…", systemImage: "square.and.arrow.up")
  120. }.help("Share a copy of this VM and all its data.")
  121. #if os(macOS)
  122. if !vm.isShortcut {
  123. Button {
  124. confirmAction = .confirmMoveVM
  125. } label: {
  126. Label("Move…", systemImage: "arrow.down.doc")
  127. }.disabled(!vm.isModifyAllowed)
  128. .help("Move this VM from internal storage to elsewhere.")
  129. }
  130. #endif
  131. Button {
  132. confirmAction = .confirmCloneVM
  133. } label: {
  134. Label("Clone…", systemImage: "doc.on.doc")
  135. }.help("Duplicate this VM along with all its data.")
  136. Button {
  137. data.busyWorkAsync {
  138. try await data.template(vm: vm)
  139. }
  140. } label: {
  141. Label("New from template…", systemImage: "doc.on.clipboard")
  142. }.help("Create a new VM with the same configuration as this one but without any data.")
  143. Divider()
  144. if vm.isShortcut {
  145. DestructiveButton {
  146. confirmAction = .confirmDeleteShortcut
  147. } label: {
  148. Label("Remove", systemImage: "trash")
  149. }.disabled(!vm.isModifyAllowed)
  150. .help("Delete this shortcut. The underlying data will not be deleted.")
  151. } else {
  152. DestructiveButton {
  153. confirmAction = .confirmDeleteVM
  154. } label: {
  155. Label("Delete", systemImage: "trash")
  156. }.disabled(!vm.isModifyAllowed)
  157. .help("Delete this VM and all its data.")
  158. }
  159. #endif
  160. }
  161. .modifier(VMShareItemModifier(isPresented: $showSharePopup, shareItem: shareItem))
  162. .modifier(VMConfirmActionModifier(vm: vm, confirmAction: $confirmAction) {
  163. if confirmAction == .confirmMoveVM {
  164. shareItem = .utmMove(vm)
  165. showSharePopup.toggle()
  166. }
  167. })
  168. }
  169. }