VMDisplayTerminalViewController.swift 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. //
  2. // Copyright © 2022 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 Foundation
  17. import SwiftTerm
  18. import SwiftUI
  19. @objc class VMDisplayTerminalViewController: VMDisplayViewController {
  20. private var terminalView: TerminalView!
  21. var vmSerialPort: CSPort {
  22. willSet {
  23. vmSerialPort.delegate = nil
  24. newValue.delegate = self
  25. terminalView.getTerminal().resetToInitialState()
  26. terminalView.getTerminal().softReset()
  27. }
  28. }
  29. private var style: UTMConfigurationTerminal?
  30. private var keyboardDelta: CGFloat = 0
  31. required init(port: CSPort, style: UTMConfigurationTerminal? = nil) {
  32. self.vmSerialPort = port
  33. super.init(nibName: nil, bundle: nil)
  34. port.delegate = self
  35. self.style = style
  36. }
  37. required init?(coder: NSCoder) {
  38. return nil
  39. }
  40. override func loadView() {
  41. super.loadView()
  42. terminalView = TerminalView(frame: makeFrame (keyboardDelta: 0))
  43. terminalView.terminalDelegate = self
  44. view.insertSubview(terminalView, at: 0)
  45. styleTerminal()
  46. }
  47. override func viewDidLoad() {
  48. super.viewDidLoad()
  49. setupKeyboardMonitor()
  50. }
  51. override func enterLive() {
  52. super.enterLive()
  53. DispatchQueue.main.async {
  54. let terminalSize = CGSize(width: self.terminalView.getTerminal().cols, height: self.terminalView.getTerminal().rows)
  55. self.delegate.displayViewSize = terminalSize
  56. }
  57. }
  58. override func showKeyboard() {
  59. terminalView.becomeFirstResponder()
  60. }
  61. override func hideKeyboard() {
  62. _ = terminalView.resignFirstResponder()
  63. }
  64. }
  65. // MARK: - Layout terminal
  66. extension VMDisplayTerminalViewController {
  67. var useAutoLayout: Bool {
  68. get { true }
  69. }
  70. func makeFrame (keyboardDelta: CGFloat, _ fn: String = #function, _ ln: Int = #line) -> CGRect
  71. {
  72. if useAutoLayout {
  73. return CGRect.zero
  74. } else {
  75. return CGRect (x: view.safeAreaInsets.left,
  76. y: view.safeAreaInsets.top,
  77. width: view.frame.width - view.safeAreaInsets.left - view.safeAreaInsets.right,
  78. height: view.frame.height - view.safeAreaInsets.top - keyboardDelta)
  79. }
  80. }
  81. func setupKeyboardMonitor ()
  82. {
  83. if #available(iOS 15.0, *), useAutoLayout {
  84. terminalView.translatesAutoresizingMaskIntoConstraints = false
  85. terminalView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
  86. terminalView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
  87. terminalView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
  88. terminalView.keyboardLayoutGuide.topAnchor.constraint(equalTo: terminalView.bottomAnchor).isActive = true
  89. } else {
  90. NotificationCenter.default.addObserver(
  91. self,
  92. selector: #selector(keyboardWillShow),
  93. name: UIWindow.keyboardWillShowNotification,
  94. object: nil)
  95. NotificationCenter.default.addObserver(
  96. self,
  97. selector: #selector(keyboardWillHide),
  98. name: UIWindow.keyboardWillHideNotification,
  99. object: nil)
  100. }
  101. }
  102. @objc private func keyboardWillShow(_ notification: NSNotification) {
  103. guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
  104. let keyboardScreenEndFrame = keyboardValue.cgRectValue
  105. let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
  106. keyboardDelta = keyboardViewEndFrame.height
  107. terminalView.frame = makeFrame(keyboardDelta: keyboardViewEndFrame.height)
  108. }
  109. @objc private func keyboardWillHide(_ notification: NSNotification) {
  110. //let key = UIResponder.keyboardFrameBeginUserInfoKey
  111. keyboardDelta = 0
  112. terminalView.frame = makeFrame(keyboardDelta: 0)
  113. }
  114. }
  115. // MARK: - Style terminal
  116. extension VMDisplayTerminalViewController {
  117. private func styleTerminal() {
  118. guard let style = style else {
  119. return
  120. }
  121. let fontSize = style.fontSize
  122. let fontName = style.font.rawValue
  123. if fontName != "" {
  124. let orig = terminalView.font
  125. let new = UIFont(name: fontName, size: CGFloat(fontSize)) ?? orig
  126. terminalView.font = new
  127. } else {
  128. let orig = terminalView.font
  129. let new = UIFont(descriptor: orig.fontDescriptor, size: CGFloat(fontSize))
  130. terminalView.font = new
  131. }
  132. if let consoleTextColor = style.foregroundColor,
  133. let textColor = Color(hexString: consoleTextColor),
  134. let consoleBackgroundColor = style.backgroundColor,
  135. let backgroundColor = Color(hexString: consoleBackgroundColor) {
  136. terminalView.nativeForegroundColor = UIColor(textColor)
  137. terminalView.nativeBackgroundColor = UIColor(backgroundColor)
  138. }
  139. }
  140. }
  141. // MARK: - TerminalViewDelegate
  142. extension VMDisplayTerminalViewController: TerminalViewDelegate {
  143. func sizeChanged(source: TerminalView, newCols: Int, newRows: Int) {
  144. delegate.displayViewSize = CGSize(width: newCols, height: newRows)
  145. }
  146. func setTerminalTitle(source: TerminalView, title: String) {
  147. }
  148. func requestOpenLink(source: TerminalView, link: String, params: [String : String]) {
  149. }
  150. func hostCurrentDirectoryUpdate(source: TerminalView, directory: String?) {
  151. }
  152. func send(source: TerminalView, data: ArraySlice<UInt8>) {
  153. delegate.displayDidAssertUserInteraction()
  154. vmSerialPort.write(Data(data))
  155. }
  156. func scrolled(source: TerminalView, position: Double) {
  157. delegate.displayDidAssertUserInteraction()
  158. }
  159. func bell(source: TerminalView) {
  160. }
  161. }
  162. // MARK: - CSPortDelegate
  163. extension VMDisplayTerminalViewController: CSPortDelegate {
  164. func portDidDisconect(_ port: CSPort) {
  165. }
  166. func port(_ port: CSPort, didError error: String) {
  167. showAlert(error, actions: nil)
  168. }
  169. func port(_ port: CSPort, didRecieveData data: Data) {
  170. if let terminalView = terminalView {
  171. let arr = [UInt8](data)[...]
  172. DispatchQueue.main.async {
  173. terminalView.feed(byteArray: arr)
  174. }
  175. }
  176. }
  177. }