فهرست منبع

display(iOS): support switching displays on a window

osy 3 سال پیش
والد
کامیت
e16eaa2d94

+ 8 - 1
Platform/iOS/Display/VMDisplayTerminalViewController.swift

@@ -20,7 +20,14 @@ import SwiftUI
 
 @objc class VMDisplayTerminalViewController: VMDisplayViewController {
     private var terminalView: TerminalView!
-    private var vmSerialPort: CSPort
+    var vmSerialPort: CSPort {
+        willSet {
+            vmSerialPort.delegate = nil
+            newValue.delegate = self
+            terminalView.getTerminal().resetToInitialState()
+            terminalView.getTerminal().softReset()
+        }
+    }
     
     private var style: UTMConfigurationTerminal?
     private var keyboardDelta: CGFloat = 0

+ 16 - 0
Platform/iOS/VMDisplayHostedView.swift

@@ -184,6 +184,22 @@ struct VMDisplayHostedView: UIViewControllerRepresentable {
                 }
             }
         }
+        switch state.device {
+        case .display(let display, _):
+            if let vc = uiViewController as? VMDisplayMetalViewController {
+                if vc.vmDisplay != display {
+                    vc.vmDisplay = display
+                }
+            }
+        case .serial(let serial, _):
+            if let vc = uiViewController as? VMDisplayTerminalViewController {
+                if vc.vmSerialPort != serial {
+                    vc.vmSerialPort = serial
+                }
+            }
+        default:
+            break
+        }
     }
     
     func makeCoordinator() -> Coordinator {

+ 8 - 0
Platform/iOS/VMSessionState.swift

@@ -132,6 +132,10 @@ extension VMSessionState: UTMSpiceIODelegate {
             // associate with the next available window
             for windowId in windows {
                 if windowDeviceMap[windowId] == nil {
+                    if windowId == primaryWindow && !display.isPrimaryDisplay {
+                        // prefer the primary display for the primary window
+                        continue
+                    }
                     windowDeviceMap[windowId] = device
                 }
             }
@@ -175,6 +179,10 @@ extension VMSessionState: UTMSpiceIODelegate {
             // associate with the next available window
             for windowId in windows {
                 if windowDeviceMap[windowId] == nil {
+                    if windowId == primaryWindow && !qemuConfig.displays.isEmpty {
+                        // prefer a GUI display over console for primary if both are available
+                        continue
+                    }
                     windowDeviceMap[windowId] = device
                 }
             }

+ 67 - 0
Platform/iOS/VMToolbarDisplayMenuView.swift

@@ -0,0 +1,67 @@
+//
+// 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 SwiftUI
+
+struct VMToolbarDisplayMenuView: View {
+    @Binding var state: VMWindowState
+    @EnvironmentObject private var session: VMSessionState
+    
+    var body: some View {
+        Menu {
+            Menu {
+                Picker("", selection: $state.device) {
+                    ForEach(session.devices) { device in
+                        switch device {
+                        case .serial(_, let index):
+                            Label("Serial \(index): \(session.qemuConfig.serials[index].target.prettyValue)", systemImage: "cable.connector").tag(device as VMWindowState.Device?)
+                        case .display(_, let index):
+                            Label("Display \(index): \(session.qemuConfig.displays[index].hardware.prettyValue)", systemImage: "display").tag(device as VMWindowState.Device?)
+                        }
+                    }
+                }
+            } label: {
+                Label("Current Window", systemImage: "rectangle.fill.on.rectangle.fill")
+            }
+        } label: {
+            Label("Display", systemImage: "rectangle.on.rectangle")
+        }.overlay(Badge(count: session.devices.count), alignment: .topTrailing)
+    }
+}
+
+private struct Badge: View {
+    let count: Int
+    
+    var body: some View {
+        if count > 1 {
+            ZStack(alignment: .center) {
+                Circle().fill(.white)
+                Image(systemName: count <= 50 ? "\(count).circle.fill" : "infinity.circle.fill")
+                    .foregroundColor(.red)
+            }.frame(width: 16, height: 16)
+            .allowsHitTesting(false)
+        } else {
+            EmptyView()
+        }
+    }
+}
+
+struct VMToolbarDisplayMenuView_Previews: PreviewProvider {
+    @State private static var state = VMWindowState()
+    static var previews: some View {
+        VMToolbarDisplayMenuView(state: $state)
+    }
+}

+ 8 - 6
Platform/iOS/VMToolbarView.swift

@@ -89,17 +89,17 @@ struct VMToolbarView: View {
                     }
                 } label: {
                     Label(state.isRunning ? "Power Off" : "Quit", systemImage: state.isRunning ? "power" : "xmark")
-                }.offset(offset(for: 7))
+                }.offset(offset(for: 8))
                 Button {
                     session.pauseResume()
                 } label: {
                     Label(state.isRunning ? "Pause" : "Play", systemImage: state.isRunning ? "pause" : "play")
-                }.offset(offset(for: 6))
+                }.offset(offset(for: 7))
                 Button {
                     state.alert = .restart
                 } label: {
                     Label("Restart", systemImage: "restart")
-                }.offset(offset(for: 5))
+                }.offset(offset(for: 6))
                 Button {
                     if case .serial(_, _) = state.device {
                         let template = session.qemuConfig.serials[state.device!.configIndex].terminal?.resizeCommand
@@ -109,12 +109,14 @@ struct VMToolbarView: View {
                     }
                 } label: {
                     Label("Zoom", systemImage: state.isViewportChanged ? "arrow.down.right.and.arrow.up.left" : "arrow.up.left.and.arrow.down.right")
-                }.offset(offset(for: 4))
+                }.offset(offset(for: 5))
                 if session.vm.hasUsbRedirection {
                     VMToolbarUSBMenuView()
-                    .offset(offset(for: 3))
+                    .offset(offset(for: 4))
                 }
                 VMToolbarDriveMenuView()
+                .offset(offset(for: 3))
+                VMToolbarDisplayMenuView(state: $state)
                 .offset(offset(for: 2))
                 Button {
                     state.isKeyboardRequested = !state.isKeyboardShown
@@ -216,7 +218,7 @@ struct VMToolbarView: View {
     
     private func offset(for index: Int) -> CGSize {
         var sub = 0
-        if !session.vm.hasUsbRedirection && index >= 3 {
+        if !session.vm.hasUsbRedirection && index >= 4 {
             sub = 1
         }
         let x = isCollapsed ? 0 : -CGFloat(index-sub)*spacing

+ 5 - 1
Platform/iOS/VMWindowState.swift

@@ -18,7 +18,7 @@ import Foundation
 
 /// Represents the UI state for a single window
 struct VMWindowState: Identifiable {
-    enum Device: Equatable {
+    enum Device: Identifiable, Hashable {
         case display(CSDisplay, Int)
         case serial(CSPort, Int)
         
@@ -28,6 +28,10 @@ struct VMWindowState: Identifiable {
             case .serial(_, let index): return index
             }
         }
+        
+        var id: Self {
+            self
+        }
     }
     
     let id = UUID()

+ 6 - 0
UTM.xcodeproj/project.pbxproj

@@ -214,6 +214,8 @@
 		84CF5DD4288DCE6400D01721 /* VMDisplayHostedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CF5DD2288DCE6400D01721 /* VMDisplayHostedView.swift */; };
 		84CF5DF3288E433F00D01721 /* SwiftUIVisualEffects in Frameworks */ = {isa = PBXBuildFile; productRef = 84CF5DF2288E433F00D01721 /* SwiftUIVisualEffects */; };
 		84CF5DF5288F558400D01721 /* VMToolbarDriveMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CF5DF4288F558400D01721 /* VMToolbarDriveMenuView.swift */; };
+		84E6F6FD289319AE00080EEF /* VMToolbarDisplayMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6F6FC289319AE00080EEF /* VMToolbarDisplayMenuView.swift */; };
+		84E6F6FE289319AE00080EEF /* VMToolbarDisplayMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6F6FC289319AE00080EEF /* VMToolbarDisplayMenuView.swift */; };
 		84F746B9276FF40900A20C87 /* VMDisplayAppleWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F746B8276FF40900A20C87 /* VMDisplayAppleWindowController.swift */; };
 		84F746BB276FF70700A20C87 /* VMDisplayQemuDisplayController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F746BA276FF70700A20C87 /* VMDisplayQemuDisplayController.swift */; };
 		84FCABBA268CE05E0036196C /* UTMQemuVirtualMachine.m in Sources */ = {isa = PBXBuildFile; fileRef = 84FCABB9268CE05E0036196C /* UTMQemuVirtualMachine.m */; };
@@ -1632,6 +1634,7 @@
 		84C60FB9268269D700B58C00 /* VMDisplayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayViewController.swift; sourceTree = "<group>"; };
 		84CF5DD2288DCE6400D01721 /* VMDisplayHostedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayHostedView.swift; sourceTree = "<group>"; };
 		84CF5DF4288F558400D01721 /* VMToolbarDriveMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMToolbarDriveMenuView.swift; sourceTree = "<group>"; };
+		84E6F6FC289319AE00080EEF /* VMToolbarDisplayMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMToolbarDisplayMenuView.swift; sourceTree = "<group>"; };
 		84F746B8276FF40900A20C87 /* VMDisplayAppleWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayAppleWindowController.swift; sourceTree = "<group>"; };
 		84F746BA276FF70700A20C87 /* VMDisplayQemuDisplayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMDisplayQemuDisplayController.swift; sourceTree = "<group>"; };
 		84FCABB8268CE05E0036196C /* UTMQemuVirtualMachine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UTMQemuVirtualMachine.h; sourceTree = "<group>"; };
@@ -2609,6 +2612,7 @@
 				CE2D955124AD4F980059923A /* VMDrivesSettingsView.swift */,
 				CE2D954C24AD4F980059923A /* VMSettingsView.swift */,
 				84C60FB62681A41B00B58C00 /* VMToolbarView.swift */,
+				84E6F6FC289319AE00080EEF /* VMToolbarDisplayMenuView.swift */,
 				84CF5DF4288F558400D01721 /* VMToolbarDriveMenuView.swift */,
 				84258C41288F806400C66366 /* VMToolbarUSBMenuView.swift */,
 				84018688288A44C20050AC51 /* VMWindowState.swift */,
@@ -3697,6 +3701,7 @@
 				CE2D930D24AD46670059923A /* qapi-events-authz.c in Sources */,
 				CE2D930E24AD46670059923A /* qapi-commands-block-core.c in Sources */,
 				8471770627CC974F00D3A50B /* DefaultTextField.swift in Sources */,
+				84E6F6FD289319AE00080EEF /* VMToolbarDisplayMenuView.swift in Sources */,
 				CE2D930F24AD46670059923A /* qapi-types-authz.c in Sources */,
 				CE2D931024AD46670059923A /* qapi-types-qdev.c in Sources */,
 				CE8813D324CD230300532628 /* ActivityView.swift in Sources */,
@@ -4220,6 +4225,7 @@
 				CE19392726DCB094005CEC17 /* RAMSlider.swift in Sources */,
 				CEA45ED4263519B5002FA97D /* qapi-types-block-core.c in Sources */,
 				CEA45ED5263519B5002FA97D /* qapi-events-transaction.c in Sources */,
+				84E6F6FE289319AE00080EEF /* VMToolbarDisplayMenuView.swift in Sources */,
 				CEA45ED6263519B5002FA97D /* VMConfigStringPicker.swift in Sources */,
 				84909A8A27CABA54005605F1 /* UTMWrappedVirtualMachine.swift in Sources */,
 				CEA45ED7263519B5002FA97D /* qapi-commands-dump.c in Sources */,