parent
1f72fbf1f1
commit
c6d52e47de
|
@ -70,7 +70,7 @@ class UTMData: ObservableObject {
|
|||
|
||||
#if os(macOS)
|
||||
/// View controller for every VM currently active
|
||||
var vmWindows: [UTMVirtualMachine: VMDisplayWindowController] = [:]
|
||||
var vmWindows: [UTMVirtualMachine: Any] = [:]
|
||||
#else
|
||||
/// View controller for currently active VM
|
||||
var vmVC: Any?
|
||||
|
@ -301,7 +301,7 @@ class UTMData: ObservableObject {
|
|||
self.busy = busy
|
||||
}
|
||||
|
||||
@MainActor private func showErrorAlert(message: String) {
|
||||
@MainActor func showErrorAlert(message: String) {
|
||||
alertMessage = AlertMessage(message)
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ struct UTMApp: App {
|
|||
.onAppear {
|
||||
appDelegate.data = data
|
||||
}
|
||||
.onReceive(.vmSessionError) { notification in
|
||||
if let message = notification.userInfo?["Message"] as? String {
|
||||
data.showErrorAlert(message: message)
|
||||
}
|
||||
}
|
||||
}.commands {
|
||||
VMCommands()
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ import Carbon.HIToolbox
|
|||
|
||||
@available(macOS 11, *)
|
||||
extension UTMData {
|
||||
func run(vm: UTMVirtualMachine) {
|
||||
var window: VMDisplayWindowController? = vmWindows[vm]
|
||||
@MainActor func run(vm: UTMVirtualMachine) {
|
||||
var window: Any? = vmWindows[vm]
|
||||
if window == nil {
|
||||
let close = { (notification: Notification) -> Void in
|
||||
self.vmWindows.removeValue(forKey: vm)
|
||||
|
@ -31,6 +31,8 @@ extension UTMData {
|
|||
if avm.appleConfig.displays.count > 0 ||
|
||||
(avm.appleConfig.serials.count > 0 && avm.appleConfig.serials.first!.mode == .builtin) {
|
||||
window = VMDisplayAppleWindowController(vm: avm, onClose: close)
|
||||
} else {
|
||||
window = VMHeadlessSessionState(for: avm, onStop: close)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +42,7 @@ extension UTMData {
|
|||
} else if qvm.config.qemuHasTerminal {
|
||||
window = VMDisplayTerminalWindowController(vm: qvm, onClose: close)
|
||||
} else {
|
||||
// FIXME: do headless
|
||||
window = VMHeadlessSessionState(for: qvm, onStop: close)
|
||||
}
|
||||
}
|
||||
if window == nil {
|
||||
|
@ -49,12 +51,15 @@ extension UTMData {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let unwrappedWindow = window {
|
||||
if let unwrappedWindow = window as? VMDisplayWindowController {
|
||||
vmWindows[vm] = unwrappedWindow
|
||||
vm.delegate = unwrappedWindow
|
||||
unwrappedWindow.showWindow(nil)
|
||||
unwrappedWindow.window!.makeMain()
|
||||
unwrappedWindow.requestAutoStart()
|
||||
} else if let unwrappedWindow = window as? VMHeadlessSessionState {
|
||||
vmWindows[vm] = unwrappedWindow
|
||||
unwrappedWindow.start()
|
||||
} else {
|
||||
logger.critical("Failed to create window controller.")
|
||||
}
|
||||
|
@ -70,7 +75,7 @@ extension UTMData {
|
|||
}
|
||||
|
||||
func close(vm: UTMVirtualMachine) {
|
||||
if let window = vmWindows.removeValue(forKey: vm) {
|
||||
if let window = vmWindows.removeValue(forKey: vm) as? VMDisplayWindowController {
|
||||
DispatchQueue.main.async {
|
||||
window.close()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
//
|
||||
// Copyright © 2022 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 Foundation
|
||||
|
||||
/// Represents the UI state for a single headless VM session.
|
||||
@MainActor class VMHeadlessSessionState: NSObject, ObservableObject {
|
||||
let vm: UTMVirtualMachine
|
||||
var onStop: ((Notification) -> Void)?
|
||||
|
||||
@Published var vmState: UTMVMState = .vmStopped
|
||||
|
||||
@Published var fatalError: String?
|
||||
|
||||
private var hasStarted: Bool = false
|
||||
|
||||
init(for vm: UTMVirtualMachine, onStop: ((Notification) -> Void)?) {
|
||||
self.vm = vm
|
||||
self.onStop = onStop
|
||||
super.init()
|
||||
vm.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
extension VMHeadlessSessionState: UTMVirtualMachineDelegate {
|
||||
nonisolated func virtualMachine(_ vm: UTMVirtualMachine, didTransitionTo state: UTMVMState) {
|
||||
Task { @MainActor in
|
||||
vmState = state
|
||||
if state == .vmStarted {
|
||||
hasStarted = true
|
||||
}
|
||||
if state == .vmStopped {
|
||||
if hasStarted {
|
||||
stop() // graceful exit
|
||||
}
|
||||
hasStarted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated func virtualMachine(_ vm: UTMVirtualMachine, didErrorWithMessage message: String) {
|
||||
Task { @MainActor in
|
||||
fatalError = message
|
||||
NotificationCenter.default.post(name: .vmSessionError, object: nil, userInfo: ["Session": self, "Message": message])
|
||||
if !hasStarted {
|
||||
// if we got an error and haven't started, then cleanup
|
||||
stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension VMHeadlessSessionState {
|
||||
func start() {
|
||||
NotificationCenter.default.post(name: .vmSessionCreated, object: nil, userInfo: ["Session": self])
|
||||
vm.requestVmStart()
|
||||
}
|
||||
|
||||
func stop() {
|
||||
NotificationCenter.default.post(name: .vmSessionEnded, object: nil, userInfo: ["Session": self])
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let vmSessionCreated = Self("VMSessionCreated")
|
||||
static let vmSessionEnded = Self("VMSessionEnded")
|
||||
static let vmSessionError = Self("VMSessionError")
|
||||
}
|
|
@ -213,6 +213,7 @@
|
|||
84B36D2927B790BE00C22685 /* DestructiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B36D2827B790BE00C22685 /* DestructiveButton.swift */; };
|
||||
84B36D2A27B790BE00C22685 /* DestructiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B36D2827B790BE00C22685 /* DestructiveButton.swift */; };
|
||||
84B36D2B27B790BE00C22685 /* DestructiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B36D2827B790BE00C22685 /* DestructiveButton.swift */; };
|
||||
84BB993A2899E8D500DF28B2 /* VMHeadlessSessionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB99392899E8D500DF28B2 /* VMHeadlessSessionState.swift */; };
|
||||
84C4D9022880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C4D9012880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift */; };
|
||||
84C4D9032880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C4D9012880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift */; };
|
||||
84C4D9042880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C4D9012880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift */; };
|
||||
|
@ -1659,6 +1660,7 @@
|
|||
84B36D2427B704C200C22685 /* UTMDownloadVMTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMDownloadVMTask.swift; sourceTree = "<group>"; };
|
||||
84B36D2827B790BE00C22685 /* DestructiveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DestructiveButton.swift; sourceTree = "<group>"; };
|
||||
84BB99382899AB1800DF28B2 /* UTMLoggingDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMLoggingDelegate.h; sourceTree = "<group>"; };
|
||||
84BB99392899E8D500DF28B2 /* VMHeadlessSessionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMHeadlessSessionState.swift; sourceTree = "<group>"; };
|
||||
84C4D9012880CA8A00EC3B2B /* VMSettingsAddDeviceMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMSettingsAddDeviceMenuView.swift; sourceTree = "<group>"; };
|
||||
84C584DC268E70F1000FCABF /* UTMVirtualMachine-Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UTMVirtualMachine-Protected.h"; sourceTree = "<group>"; };
|
||||
84C584DE268E95B3000FCABF /* UTMLegacyAppleConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMLegacyAppleConfiguration.swift; sourceTree = "<group>"; };
|
||||
|
@ -2629,6 +2631,7 @@
|
|||
84C584E2268F8AE7000FCABF /* VMQEMUSettingsView.swift */,
|
||||
CE2D953D24AD4F980059923A /* VMSettingsView.swift */,
|
||||
CEF0300526A25A6900667B63 /* VMWizardView.swift */,
|
||||
84BB99392899E8D500DF28B2 /* VMHeadlessSessionState.swift */,
|
||||
53A0BDD426D79FE40010EDC5 /* SavePanel.swift */,
|
||||
CE2D954124AD4F980059923A /* Info.plist */,
|
||||
FFB02A8E266CB09C006CD71A /* InfoPlist.strings */,
|
||||
|
@ -3993,6 +3996,7 @@
|
|||
2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */,
|
||||
CE0B6D5B24AD584D00FE012D /* qapi-visit-misc-target.c in Sources */,
|
||||
CE0B6D0C24AD56C300FE012D /* error.c in Sources */,
|
||||
84BB993A2899E8D500DF28B2 /* VMHeadlessSessionState.swift in Sources */,
|
||||
CE2D955A24AD4F980059923A /* VMToolbarModifier.swift in Sources */,
|
||||
CED814ED24C7C2850042F0F1 /* VMConfigInfoView.swift in Sources */,
|
||||
2CE8EB0A2572E173000E2EBB /* qapi-visit-block-export.c in Sources */,
|
||||
|
|
Loading…
Reference in New Issue