VMWizardHardwareView.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. //
  2. // Copyright © 2021 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. #if canImport(Virtualization)
  18. import Virtualization
  19. #endif
  20. struct VMWizardHardwareView: View {
  21. private enum SupportedMachine: CaseIterable, Identifiable {
  22. case quadra800
  23. //case powerMacG3Beige
  24. case powerMacG4
  25. //case powerMacG5
  26. case i440FX
  27. case q35
  28. case arm64Virt
  29. case riscv64Virt
  30. var id: Self { self }
  31. var title: LocalizedStringKey {
  32. switch self {
  33. case .quadra800: "Macintosh Quadra 800 (1993, M68K)"
  34. //case .powerMacG3Beige: "Power Macintosh G3 (1997, Beige)"
  35. case .powerMacG4: "Power Macintosh G4 (1999, PPC)"
  36. //case .powerMacG5: "Power Macintosh G5 (2003, PPC64)"
  37. case .i440FX: "Intel i440FX based PC (1996, i386)"
  38. case .q35: "Intel ICH9 based PC (2009, x86_64)"
  39. case .arm64Virt: "ARM64 virtual machine (2014, ARM64)"
  40. case .riscv64Virt: "RISC-V64 virtual machine (2018, RISC-V64)"
  41. }
  42. }
  43. var architecture: QEMUArchitecture {
  44. switch self {
  45. case .quadra800: return .m68k
  46. //case .powerMacG3Beige: return .ppc
  47. case .powerMacG4: return .ppc
  48. //case .powerMacG5: return .ppc64
  49. case .i440FX: return .i386
  50. case .q35: return .x86_64
  51. case .arm64Virt: return .aarch64
  52. case .riscv64Virt: return .riscv64
  53. }
  54. }
  55. var target: any QEMUTarget {
  56. switch self {
  57. case .quadra800: return QEMUTarget_m68k.q800
  58. //case .powerMacG3Beige: return QEMUTarget_ppc.g3beige
  59. case .powerMacG4: return QEMUTarget_ppc.mac99
  60. //case .powerMacG5: return QEMUTarget_ppc.mac99
  61. case .i440FX: return QEMUTarget_i386.pc
  62. case .q35: return QEMUTarget_x86_64.q35
  63. case .arm64Virt: return QEMUTarget_aarch64.virt
  64. case .riscv64Virt: return QEMUTarget_riscv64.virt
  65. }
  66. }
  67. var minRam: Int {
  68. switch self {
  69. case .quadra800: return 8
  70. //case .powerMacG3Beige: return 32
  71. case .powerMacG4: return 64
  72. //case .powerMacG5: return 64
  73. default: return 0
  74. }
  75. }
  76. var maxRam: Int {
  77. switch self {
  78. case .quadra800: return 1024
  79. //case .powerMacG3Beige: return 2047
  80. case .powerMacG4: return 2048
  81. //case .powerMacG5: return 2048
  82. default: return 0
  83. }
  84. }
  85. var defaultRam: Int {
  86. switch self {
  87. case .quadra800: return 128
  88. //case .powerMacG3Beige: return 512
  89. case .powerMacG4: return 512
  90. //case .powerMacG5: return 512
  91. case .i440FX: return 512
  92. #if os(macOS)
  93. default: return 4096
  94. #else
  95. default: return 512
  96. #endif
  97. }
  98. }
  99. var defaultStorageGiB: Int {
  100. switch self {
  101. case .quadra800, .powerMacG4: return 2
  102. case .i440FX: return 2
  103. #if os(macOS)
  104. default: return 64
  105. #else
  106. default: return 2
  107. #endif
  108. }
  109. }
  110. var maxSupportedCores: Int {
  111. switch self {
  112. case .quadra800, .powerMacG4: return 1
  113. default: return 0
  114. }
  115. }
  116. var isLegacyHardware: Bool {
  117. switch self {
  118. case .quadra800, .powerMacG4, .i440FX: return true
  119. default: return false
  120. }
  121. }
  122. func isSupported(running os: VMWizardOS) -> Bool {
  123. switch os {
  124. case .Other: return true
  125. case .macOS: return [.arm64Virt].contains(self)
  126. case .Linux: return true
  127. case .Windows: return [.i440FX, .q35, .arm64Virt].contains(self)
  128. case .ClassicMacOS: return [.quadra800, .powerMacG4].contains(self)
  129. }
  130. }
  131. static func `default`(for os: VMWizardOS) -> Self {
  132. switch os {
  133. case .Other: return .q35
  134. case .macOS: return .arm64Virt
  135. case .Linux: return .q35
  136. case .Windows: return .q35
  137. case .ClassicMacOS: return .powerMacG4
  138. }
  139. }
  140. }
  141. @ObservedObject var wizardState: VMWizardState
  142. @State private var isExpertMode: Bool = false
  143. @State private var selectedMachine: SupportedMachine?
  144. var minCores: Int {
  145. #if canImport(Virtualization)
  146. VZVirtualMachineConfiguration.minimumAllowedCPUCount
  147. #else
  148. 1
  149. #endif
  150. }
  151. var maxCores: Int {
  152. #if canImport(Virtualization)
  153. VZVirtualMachineConfiguration.maximumAllowedCPUCount
  154. #else
  155. Int(sysctlIntRead("hw.ncpu"))
  156. #endif
  157. }
  158. var minMemoryMib: Int {
  159. #if canImport(Virtualization)
  160. Int(VZVirtualMachineConfiguration.minimumAllowedMemorySize / UInt64(wizardState.bytesInMib))
  161. #else
  162. 8
  163. #endif
  164. }
  165. var maxMemoryMib: Int {
  166. #if canImport(Virtualization)
  167. Int(VZVirtualMachineConfiguration.maximumAllowedMemorySize / UInt64(wizardState.bytesInMib))
  168. #else
  169. sysctlIntRead("hw.memsize")
  170. #endif
  171. }
  172. var body: some View {
  173. VMWizardContent("Hardware") {
  174. if !wizardState.useVirtualization {
  175. Toggle("Expert Mode", isOn: $isExpertMode)
  176. .help("List all supported hardware. May require manual configuration to boot.")
  177. }
  178. if !wizardState.useVirtualization && isExpertMode {
  179. Section {
  180. VMConfigConstantPicker(selection: $wizardState.systemArchitecture)
  181. .onChange(of: wizardState.systemArchitecture) { newValue in
  182. wizardState.systemTarget = newValue.targetType.default
  183. }
  184. } header: {
  185. Text("Architecture")
  186. }
  187. Section {
  188. VMConfigConstantPicker(selection: $wizardState.systemTarget, type: wizardState.systemArchitecture.targetType)
  189. } header: {
  190. Text("System")
  191. }
  192. } else if !isExpertMode {
  193. Picker("Machine", selection: $selectedMachine) {
  194. ForEach(SupportedMachine.allCases.filter({ $0.isSupported(running: wizardState.operatingSystem )})) { system in
  195. Text(system.title).tag(system)
  196. }
  197. }.pickerStyle(.inline)
  198. .onChange(of: selectedMachine) { newValue in
  199. guard let newValue = newValue else {
  200. return
  201. }
  202. wizardState.systemArchitecture = newValue.architecture
  203. wizardState.systemTarget = newValue.target
  204. wizardState.systemMemoryMib = newValue.defaultRam
  205. wizardState.systemCpuCount = newValue.maxSupportedCores
  206. wizardState.storageSizeGib = newValue.defaultStorageGiB
  207. wizardState.legacyHardware = newValue.isLegacyHardware
  208. }
  209. }
  210. Section {
  211. RAMSlider(systemMemory: $wizardState.systemMemoryMib) { _ in
  212. let selectedMax = selectedMachine?.maxRam ?? 0
  213. let validMax = selectedMax > 0 ? selectedMax : maxMemoryMib
  214. if wizardState.systemMemoryMib > validMax {
  215. wizardState.systemMemoryMib = validMax
  216. }
  217. let validMin = selectedMachine?.minRam ?? 0
  218. if wizardState.systemMemoryMib < validMin {
  219. wizardState.systemMemoryMib = validMin
  220. }
  221. }
  222. } header: {
  223. Text("Memory")
  224. }
  225. if isExpertMode || selectedMachine?.maxSupportedCores == 0 {
  226. Section {
  227. HStack {
  228. Stepper(value: $wizardState.systemCpuCount, in: minCores...maxCores) {
  229. Text("CPU Cores")
  230. }
  231. NumberTextField("", number: $wizardState.systemCpuCount, prompt: "Default", onEditingChanged: { _ in
  232. guard wizardState.systemCpuCount != 0 else {
  233. return
  234. }
  235. if wizardState.systemCpuCount < minCores {
  236. wizardState.systemCpuCount = minCores
  237. } else if wizardState.systemCpuCount > maxCores {
  238. wizardState.systemCpuCount = maxCores
  239. }
  240. })
  241. .frame(width: 80)
  242. .multilineTextAlignment(.trailing)
  243. }
  244. } header: {
  245. Text("CPU")
  246. }
  247. }
  248. if !wizardState.useAppleVirtualization && wizardState.operatingSystem == .Linux {
  249. DetailedSection("Display Output", description: "There are known issues in some newer Linux drivers including black screen, broken compositing, and apps failing to render.") {
  250. Toggle("Enable display output", isOn: $wizardState.isDisplayEnabled)
  251. .onChange(of: wizardState.isDisplayEnabled) { newValue in
  252. if !newValue {
  253. wizardState.isGLEnabled = false
  254. }
  255. }
  256. Toggle("Enable hardware OpenGL acceleration", isOn: $wizardState.isGLEnabled)
  257. .disabled(!wizardState.isDisplayEnabled)
  258. }
  259. }
  260. if !wizardState.useVirtualization && isExpertMode {
  261. Section {
  262. Toggle("Legacy Hardware", isOn: $wizardState.legacyHardware)
  263. .help("If checked, emulated devices with higher compatibility will be instantiated at the cost of performance.")
  264. } header: {
  265. Text("Options")
  266. }
  267. }
  268. }
  269. .textFieldStyle(.roundedBorder)
  270. .onAppear {
  271. if wizardState.useVirtualization {
  272. isExpertMode = true
  273. selectedMachine = nil
  274. #if arch(arm64)
  275. wizardState.systemArchitecture = .aarch64
  276. #elseif arch(x86_64)
  277. wizardState.systemArchitecture = .x86_64
  278. #else
  279. #error("Unsupported architecture.")
  280. #endif
  281. wizardState.systemTarget = wizardState.systemArchitecture.targetType.default
  282. wizardState.legacyHardware = false
  283. } else if selectedMachine == nil {
  284. selectedMachine = SupportedMachine.default(for: wizardState.operatingSystem)
  285. wizardState.systemArchitecture = selectedMachine!.architecture
  286. wizardState.systemTarget = selectedMachine!.target
  287. wizardState.systemMemoryMib = selectedMachine!.defaultRam
  288. wizardState.systemCpuCount = selectedMachine!.maxSupportedCores
  289. wizardState.storageSizeGib = selectedMachine!.defaultStorageGiB
  290. wizardState.legacyHardware = selectedMachine!.isLegacyHardware
  291. }
  292. }
  293. }
  294. private func sysctlIntRead(_ name: String) -> Int {
  295. var value: Int = 0
  296. var size = MemoryLayout<UInt64>.size
  297. sysctlbyname(name, &value, &size, nil, 0)
  298. return value
  299. }
  300. }
  301. struct VMWizardHardwareView_Previews: PreviewProvider {
  302. @StateObject static var wizardState = VMWizardState()
  303. static var previews: some View {
  304. VMWizardHardwareView(wizardState: wizardState)
  305. }
  306. }