diff --git a/Managers/UTMQemuSystem.m b/Managers/UTMQemuSystem.m index 1591a5748..e56834576 100644 --- a/Managers/UTMQemuSystem.m +++ b/Managers/UTMQemuSystem.m @@ -645,6 +645,9 @@ - (void)argsRequired { error:nil]]; [self pushArgv:self.resourceURL.path]; [self pushArgv:@"-S"]; // startup stopped + NSURL *spiceSocketURL = self.configuration.spiceSocketURL; + [self pushArgv:@"-spice"]; + [self pushArgv:[NSString stringWithFormat:@"unix=on,addr=%@,disable-ticketing=on,image-compression=off,playback-compression=off,streaming-video=off,gl=%@", spiceSocketURL.path, self.isGLOn ? @"on" : @"off"]]; [self pushArgv:@"-chardev"]; [self pushArgv:@"spiceport,id=org.qemu.monitor.qmp,name=org.qemu.monitor.qmp.0"]; [self pushArgv:@"-mon"]; @@ -669,9 +672,6 @@ - (void)argsRequired { [self pushArgv: @"-serial"]; [self pushArgv: @"chardev:term0"]; } else { - NSURL *spiceSocketURL = self.configuration.spiceSocketURL; - [self pushArgv:@"-spice"]; - [self pushArgv:[NSString stringWithFormat:@"unix=on,addr=%@,disable-ticketing=on,image-compression=off,playback-compression=off,streaming-video=off,gl=%@", spiceSocketURL.path, self.isGLOn ? @"on" : @"off"]]; if (!self.isSparc) { // SPARC uses -vga (above) [self pushArgv:@"-device"]; [self pushArgv:self.configuration.displayCard]; diff --git a/Platform/macOS/Display/VMDisplayMetalWindowController.swift b/Platform/macOS/Display/VMDisplayMetalWindowController.swift index 7f0dcfbc9..027dbe53b 100644 --- a/Platform/macOS/Display/VMDisplayMetalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayMetalWindowController.swift @@ -99,7 +99,6 @@ class VMDisplayMetalWindowController: VMDisplayQemuWindowController { self.syncCapsLock(with: event.modifierFlags) } } - qemuVM.ioDelegate = self super.enterLive() resizeConsoleToolbarItem.isEnabled = false // disable item } diff --git a/Platform/macOS/Display/VMDisplayQemuDisplayController.swift b/Platform/macOS/Display/VMDisplayQemuDisplayController.swift index 126ee7cc0..f1409e3dc 100644 --- a/Platform/macOS/Display/VMDisplayQemuDisplayController.swift +++ b/Platform/macOS/Display/VMDisplayQemuDisplayController.swift @@ -41,6 +41,7 @@ class VMDisplayQemuWindowController: VMDisplayWindowController { } override func enterLive() { + qemuVM.ioDelegate = self startPauseToolbarItem.isEnabled = true #if arch(x86_64) if vmQemuConfig.useHypervisor { diff --git a/Platform/macOS/Display/VMDisplayTerminalWindowController.swift b/Platform/macOS/Display/VMDisplayTerminalWindowController.swift index 1a84e2295..4c86ca80c 100644 --- a/Platform/macOS/Display/VMDisplayTerminalWindowController.swift +++ b/Platform/macOS/Display/VMDisplayTerminalWindowController.swift @@ -1,5 +1,5 @@ // -// Copyright © 2020 osy. All rights reserved. +// 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. @@ -14,44 +14,19 @@ // limitations under the License. // -import SwiftUI -import WebKit +import SwiftTerm private let kVMDefaultResizeCmd = "stty cols $COLS rows $ROWS\\n" -private enum JSCommand: String { - case sendInput = "UTMSendInput" - case debug = "UTMDebug" - case sendGesture = "UTMSendGesture" - case sendTerminalSize = "UTMSendTerminalSize" -} - class VMDisplayTerminalWindowController: VMDisplayQemuWindowController { - var webView: WKWebView! - private var columns: Int? - private var rows: Int? + private var terminalView: TerminalView! + private weak var vmSerialPort: CSPort? override func windowDidLoad() { - let webConfig = WKWebViewConfiguration() - webConfig.userContentController.add(self, name: JSCommand.sendInput.rawValue) - webConfig.userContentController.add(self, name: JSCommand.debug.rawValue) - webConfig.userContentController.add(self, name: JSCommand.sendGesture.rawValue) - webConfig.userContentController.add(self, name: JSCommand.sendTerminalSize.rawValue) - webView = WKWebView(frame: displayView.bounds, configuration: webConfig) - webView.autoresizingMask = [.width, .height] - webView.setValue(false, forKey: "drawsBackground") - displayView.addSubview(webView) - - // load terminal.html - guard let resourceURL = Bundle.main.resourceURL else { - showErrorAlert(NSLocalizedString("Cannot find bundle resources.", comment: "VMDisplayTerminalWindowController")) - logger.critical("Cannot find system default Metal device.") - return - } - let indexFile = resourceURL.appendingPathComponent("terminal.html") - webView.navigationDelegate = self - self.webView.loadFileURL(indexFile, allowingReadAccessTo: resourceURL) - + terminalView = TerminalView(frame: displayView.bounds) + terminalView.terminalDelegate = self + terminalView.autoresizingMask = [.width, .height] + displayView.addSubview(terminalView) super.windowDidLoad() } @@ -60,100 +35,66 @@ class VMDisplayTerminalWindowController: VMDisplayQemuWindowController { captureMouseToolbarItem.isEnabled = false } - // MARK: - Resizing console override func resizeConsoleButtonPressed(_ sender: Any) { - guard let columns = self.columns else { - logger.error("Did not get columns from page") - return - } - guard let rows = self.rows else { - logger.error("Did not get rows from page") - return - } + let cols = terminalView.getTerminal().cols + let rows = terminalView.getTerminal().rows let template = vmQemuConfig?.consoleResizeCommand ?? kVMDefaultResizeCmd let cmd = template - .replacingOccurrences(of: "$COLS", with: String(columns)) + .replacingOccurrences(of: "$COLS", with: String(cols)) .replacingOccurrences(of: "$ROWS", with: String(rows)) .replacingOccurrences(of: "\\n", with: "\n") - qemuVM.sendInput(cmd) - } -} - -extension VMDisplayTerminalWindowController: WKNavigationDelegate { - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - updateSettings() + vmSerialPort?.write(cmd.data(using: .nonLossyASCII)!) } - func updateSettings() { - if let consoleTextColor = vmQemuConfig?.consoleTextColor, - let srgbConsoleTextColor = Color(hexString: consoleTextColor)?.cgColor?.sRGBhexString, - let consoleBackgroundColor = vmQemuConfig?.consoleBackgroundColor, - let srgbConsoleBackgroundColor = Color(hexString: consoleBackgroundColor)?.cgColor?.sRGBhexString { - webView.evaluateJavaScript("changeColor('\(srgbConsoleTextColor)', '\(srgbConsoleBackgroundColor)');") { (_, err) in - if let error = err { - logger.error("changeColor error: \(error)") - } - } - } - if let consoleFont = vmQemuConfig?.consoleFont { - let consoleFontSize = vmQemuConfig?.consoleFontSize?.intValue ?? 12 - webView.evaluateJavaScript("changeFont('\(consoleFont)', \(consoleFontSize));") { (_, err) in - if let error = err { - logger.error("changeFont error: \(error)") - } - } + override func spiceDidCreateSerial(_ serial: CSPort) { + if vmSerialPort == nil { + vmSerialPort = serial + serial.delegate = self } - if let cursorBlink = vmQemuConfig?.consoleCursorBlink { - webView.evaluateJavaScript("setCursorBlink(\(cursorBlink ? "true" : "false"));") { (_, err) in - if let error = err { - logger.error("setCursorBlink error: \(error)") - } - } + } + + override func spiceDidDestroySerial(_ serial: CSPort) { + if vmSerialPort == serial { + serial.delegate = nil + vmSerialPort = nil } } } -extension VMDisplayTerminalWindowController: WKScriptMessageHandler { - func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { - guard let command = JSCommand.init(rawValue: message.name) else { - logger.error("Unknown command: \(message.name)") - return - } - switch command { - case .sendInput: - guard let body = message.body as? String else { - logger.error("Body is not of string type") - return - } - qemuVM.sendInput(body) - case .debug: - logger.debug("JS debug: \(message.body)") - case .sendGesture: - logger.debug("Ignoring gesture") - case .sendTerminalSize: - guard let body = message.body as? [NSNumber], body.count == 2 else { - logger.error("Body is not an array of two numbers") - return - } - columns = body[0].intValue - rows = body[1].intValue +extension VMDisplayTerminalWindowController: TerminalViewDelegate { + func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) { + } + + func setTerminalTitle(source: TerminalView, title: String) { + window!.subtitle = title + } + + func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) { + } + + func send(source: TerminalView, data: ArraySlice) { + if let vmSerialPort = vmSerialPort { + vmSerialPort.write(Data(data)) } } + + func scrolled(source: TerminalView, position: Double) { + } } -extension VMDisplayTerminalWindowController: UTMTerminalDelegate { - func terminal(_ terminal: UTMTerminal, didReceive data: Data) { - var dataString = "[" - for i in data.indices { - dataString = dataString.appendingFormat("%u,", data[i]) - } - dataString = dataString + "]" - let jsString = "writeData(new Uint8Array(\(dataString)));" - DispatchQueue.main.async { - self.webView.evaluateJavaScript(jsString) { (_, err) in - if let error = err { - logger.error("JS evaluation failed: \(error)") - } +extension VMDisplayTerminalWindowController: CSPortDelegate { + func portDidDisconect(_ port: CSPort) { + } + + func port(_ port: CSPort, didError error: String) { + showErrorAlert(error) + } + + func port(_ port: CSPort, didRecieveData data: Data) { + if let terminalView = terminalView { + let arr = [UInt8](data)[...] + DispatchQueue.main.async { + terminalView.feed(byteArray: arr) } } }