179 lines
7.5 KiB
Swift
179 lines
7.5 KiB
Swift
//
|
|
// Copyright © 2021 osy. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import Virtualization
|
|
import SwiftUI
|
|
|
|
@available(macOS 11, *)
|
|
struct VMConfigAppleBootView: View {
|
|
private enum BootloaderSelection: Int, Identifiable {
|
|
var id: Int {
|
|
self.rawValue
|
|
}
|
|
case kernel
|
|
case ramdisk
|
|
case ipsw
|
|
case unsupported
|
|
}
|
|
|
|
@Binding var config: UTMAppleConfigurationSystem
|
|
@EnvironmentObject private var data: UTMData
|
|
@State private var operatingSystem: UTMAppleConfigurationBoot.OperatingSystem = .none
|
|
@State private var alertBootloaderSelection: BootloaderSelection?
|
|
@State private var importBootloaderSelection: BootloaderSelection?
|
|
@State private var importFileShown: Bool = false
|
|
|
|
private var currentOperatingSystem: UTMAppleConfigurationBoot.OperatingSystem {
|
|
config.boot.operatingSystem
|
|
}
|
|
|
|
var body: some View {
|
|
Form {
|
|
VMConfigConstantPicker("Operating System", selection: $operatingSystem)
|
|
Picker("Bootloader", selection: $config.boot.hasUefiBoot) {
|
|
Text(operatingSystem.prettyValue).tag(false)
|
|
if #available(macOS 13, *) {
|
|
Text("UEFI").tag(true)
|
|
}
|
|
}
|
|
.onAppear {
|
|
operatingSystem = currentOperatingSystem
|
|
}
|
|
.onChange(of: operatingSystem) { newValue in
|
|
guard newValue != currentOperatingSystem else {
|
|
return
|
|
}
|
|
if newValue == .linux {
|
|
if #available(macOS 13, *) {
|
|
config.boot.hasUefiBoot = true
|
|
config.boot.operatingSystem = .linux
|
|
} else {
|
|
alertBootloaderSelection = .kernel
|
|
}
|
|
} else if newValue == .macOS {
|
|
if #available(macOS 12, *) {
|
|
alertBootloaderSelection = .ipsw
|
|
} else {
|
|
alertBootloaderSelection = .unsupported
|
|
}
|
|
} else {
|
|
config.boot.operatingSystem = .none
|
|
}
|
|
// don't change display until AFTER file selected
|
|
importBootloaderSelection = nil
|
|
operatingSystem = currentOperatingSystem
|
|
}.onChange(of: config.boot.hasUefiBoot) { newValue in
|
|
if !newValue && operatingSystem == .linux && config.boot.linuxKernelURL == nil {
|
|
alertBootloaderSelection = .kernel
|
|
operatingSystem = .none
|
|
} else if newValue {
|
|
config.genericPlatform = UTMAppleConfigurationGenericPlatform()
|
|
}
|
|
}.alert(item: $alertBootloaderSelection) { selection in
|
|
let okay = Alert.Button.default(Text("OK")) {
|
|
importBootloaderSelection = selection
|
|
importFileShown = true
|
|
}
|
|
switch selection {
|
|
case .kernel:
|
|
return Alert(title: Text("Please select an uncompressed Linux kernel image."), dismissButton: okay)
|
|
case .ipsw:
|
|
return Alert(title: Text("Please select a macOS recovery IPSW."), primaryButton: okay, secondaryButton: .cancel())
|
|
case .unsupported:
|
|
return Alert(title: Text("This operating system is unsupported on your machine."))
|
|
default:
|
|
return Alert(title: Text("Select a file."), dismissButton: okay)
|
|
}
|
|
}.fileImporter(isPresented: $importFileShown,
|
|
allowedContentTypes: [importBootloaderSelection == .ipsw ? .ipsw : .data],
|
|
onCompletion: selectImportedFile)
|
|
|
|
if operatingSystem == .linux && !config.boot.hasUefiBoot {
|
|
Section(header: Text("Linux Settings")) {
|
|
FileBrowseField("Kernel Image", url: $config.boot.linuxKernelURL, isFileImporterPresented: $importFileShown, hasClearButton: false) {
|
|
importBootloaderSelection = .kernel
|
|
}
|
|
FileBrowseField("Ramdisk (optional)", url: $config.boot.linuxInitialRamdiskURL, isFileImporterPresented: $importFileShown) {
|
|
importBootloaderSelection = .ramdisk
|
|
}
|
|
TextField("Boot arguments", text: $config.boot.linuxCommandLine.bound)
|
|
}
|
|
} else if #available(macOS 12, *), operatingSystem == .macOS {
|
|
#if arch(arm64)
|
|
Section(header: Text("macOS Settings")) {
|
|
FileBrowseField("IPSW Install Image", url: $config.boot.macRecoveryIpswURL, isFileImporterPresented: $importFileShown) {
|
|
importBootloaderSelection = .ipsw
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}.onAppear {
|
|
operatingSystem = currentOperatingSystem
|
|
}
|
|
}
|
|
|
|
private func selectImportedFile(result: Result<URL, Error>) {
|
|
// reset operating system to old value
|
|
guard let selection = importBootloaderSelection else {
|
|
return
|
|
}
|
|
data.busyWorkAsync {
|
|
let url = try result.get()
|
|
switch selection {
|
|
case .ipsw:
|
|
if #available(macOS 12, *) {
|
|
#if arch(arm64)
|
|
let image = try await VZMacOSRestoreImage.image(from: url)
|
|
guard let model = image.mostFeaturefulSupportedConfiguration?.hardwareModel else {
|
|
throw NSLocalizedString("Your machine does not support running this IPSW.", comment: "VMConfigAppleBootView")
|
|
}
|
|
await MainActor.run {
|
|
config.macPlatform = UTMAppleConfigurationMacPlatform(newHardware: model)
|
|
config.boot.operatingSystem = .macOS
|
|
config.boot.macRecoveryIpswURL = url
|
|
}
|
|
#endif
|
|
}
|
|
case .kernel:
|
|
await MainActor.run {
|
|
config.genericPlatform = UTMAppleConfigurationGenericPlatform()
|
|
config.boot.operatingSystem = .linux
|
|
config.boot.linuxKernelURL = url
|
|
config.boot.hasUefiBoot = false
|
|
}
|
|
case .ramdisk:
|
|
await MainActor.run {
|
|
config.boot.linuxInitialRamdiskURL = url
|
|
}
|
|
case .unsupported:
|
|
break
|
|
}
|
|
await MainActor.run {
|
|
operatingSystem = currentOperatingSystem
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@available(macOS 12, *)
|
|
struct VMConfigAppleBootView_Previews: PreviewProvider {
|
|
@State static private var config = UTMAppleConfigurationSystem()
|
|
|
|
static var previews: some View {
|
|
VMConfigAppleBootView(config: $config)
|
|
}
|
|
}
|